From: Robert Lipe Date: Mon, 29 Feb 2016 00:48:33 +0000 (-0600) Subject: Drop delbin, old google format. an1 test used to use google format, but X-Git-Tag: archive/raspbian/1.10.0+ds-2+rpi1~1^2~12^2~9^2~28 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=619d14bbfd19306a0667090d65d616f59e67e14a;p=gpsbabel.git Drop delbin, old google format. an1 test used to use google format, but an1 is on the list to be chopped in the next round. --- diff --git a/delbin.cc b/delbin.cc deleted file mode 100644 index 56735fc2a..000000000 --- a/delbin.cc +++ /dev/null @@ -1,3373 +0,0 @@ -/* - DeLorme PN-20/40 USB "DeLBin" protocol - - Copyright (C) 2009 Paul Cornett, pc-gpsb at bullseye.com - Copyright (C) 2005-2014 Robert Lipe, robertlipe+source@gpsbabel.orgg - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA - - */ - -#include "defs.h" -#include "src/core/xmltag.h" -#include -#include /* for atoi, sprintf */ -#include // atoi - -#define MYNAME "delbin" -static short_handle mkshort_handle; - -/* -Device documentation: -"DeLorme Binary GPS Format", delbin_user_interface_format_176.pdf -obtained here: http://forum.delorme.com/viewtopic.php?t=13846 - -Notes: -Initial development was done with a PN-40, firmware 2.4.123299. The test -device was upgraded to firmware 2.5.165506 during development. - -The "data size" in the message header includes the 4 trailer bytes, so it -is really the size of the whole message minus the header. - -Messages do not always start at the beginning of a packet. Every once in a -while, the start of the next message directly follows the end of the previous -one, in the same packet. - -The time before an unacknowledged message will be retransmitted by the -device is on the order of 2 to 4 seconds. - -Retrieving all tracks at once (using code 0 in message 0xb031) does not -seem to work, it hangs after the first track, maybe waiting for some -undocumented response message. - -Character encoding is not documented, appears to be 8859-1. - -The undocumented messages 0xaa01, 0xb015, 0xb016 and the use of the -"reserved" byte in message 0xb012 were discovered by examining the data -transferred between the device and DeLorme Topo 8.0. They may have been -added in the PN-40 2.5 firmware. -*/ - -//----------------------------------------------------------------------------- -// interface to platform-specific device I/O -typedef struct { - void (*init)(const QString& name); - void (*deinit)(void); - unsigned(*packet_read)(void*); - unsigned(*packet_write)(const void*, unsigned); -} delbin_os_ops_t; - -// really static, only extern so it can be forward declared -extern delbin_os_ops_t delbin_os_ops; - -static unsigned delbin_os_packet_size; -//----------------------------------------------------------------------------- - -// number of times to attempt a transfer before giving up -#define ATTEMPT_MAX 2 -// seconds to wait for expected message (actual time will be somewhat -// indeterminate, but at least READ_TIMEOUT - 1) -#define READ_TIMEOUT 6 - -// debug output: low, medium, high, higher -#define DBGLVL_L 1 -#define DBGLVL_M 2 -#define DBGLVL_H 3 -#define DBGLVL_H2 4 - -// Multiple unit support. -#define DELBIN_MAX_UNITS 32 -static struct { - unsigned int unit_number; - const char* unit_serial_number; - const char* unit_name; -} delbin_unit_info[DELBIN_MAX_UNITS]; -static int n_delbin_units; - -#define UNKNOWN_ELEV -2000000 - -#define sizeofarray(x) (sizeof(x) / sizeof(x[0])) - -static char* opt_getposn = NULL; -static char* opt_logs = NULL; -static char* opt_long_notes = NULL; -static char* opt_nuke_wpt = NULL; -static char* opt_nuke_trk = NULL; -static char* opt_nuke_rte = NULL; -/* If true, Order hint to match Cache Register and Topo 7 */ -static char* opt_hint_at_end = NULL; -static char* opt_gcsym = NULL; - - -static arglist_t delbin_args[] = { - { - "get_posn", &opt_getposn, "Return current position as a waypoint", - NULL, ARGTYPE_BOOL, ARG_NOMINMAX - }, - { - "logs", &opt_logs, "Include groundspeak logs when writing", - NULL, ARGTYPE_BOOL, ARG_NOMINMAX - }, - { - "long_notes", &opt_long_notes, "Use long waypoint notes regardless of PN version", - NULL, ARGTYPE_BOOL, ARG_NOMINMAX - }, - { - "nukewpt", &opt_nuke_wpt, "Delete all waypoints before sending", NULL, ARGTYPE_BOOL, - ARG_NOMINMAX - }, - { - "nuketrk", &opt_nuke_trk, "Delete all tracks before sending", NULL, ARGTYPE_BOOL, - ARG_NOMINMAX - }, - { - "nukerte", &opt_nuke_rte, "Delete all routes before sending", NULL, ARGTYPE_BOOL, - ARG_NOMINMAX - }, - {"hint_at_end", &opt_hint_at_end, "If true, geocache hint at end of text", NULL, ARGTYPE_BOOL, ARG_NOMINMAX }, - {"gcsym", &opt_gcsym, "If set to 0, prefer user-provided symbols over Groundspeaks ones for geocaches", "1", ARGTYPE_BOOL, ARG_NOMINMAX }, - ARG_TERMINATOR -}; - -// Whether device understands message 0xb016 -static int use_extended_notes; - -// Device capabilities -static unsigned device_max_waypoint; - -static const char* waypoint_symbol(unsigned index); -static unsigned waypoint_symbol_index(const char* name); -static int track_color(unsigned index); -static unsigned track_color_index(int bgr); - -static unsigned waypoint_i; -static unsigned waypoint_n; -static Waypoint** wp_array; - -//----------------------------------------------------------------------------- -// Message ids and sizes. Only the needed ones are here. -// Note that "in" and "out" ids are named as in the device documentation, -// so "in" means to the device, "out" means from. -#define MSG_ACK 0xaa00 -#define MSG_BREAK 0xaa02 -#define MSG_BREAK_SIZE 33 -#define MSG_CAPABILITIES 0xb001 -#define MSG_DELETE 0xb005 -#define MSG_DELETE_SIZE 67 -#define MSG_ERROR 0xa003 -#define MSG_NAVIGATION 0xa010 -#define MSG_REQUEST_ROUTES 0xb051 -#define MSG_REQUEST_ROUTES_SIZE 65 -#define MSG_REQUEST_TRACKS 0xb031 -#define MSG_REQUEST_TRACKS_SIZE 33 -#define MSG_REQUEST_WAYPOINTS 0xb012 -#define MSG_REQUEST_WAYPOINTS_SIZE 15 -#define MSG_ROUTE_COUNT 0xb050 -#define MSG_ROUTE_HEADER_IN 0xb055 -#define MSG_ROUTE_HEADER_OUT 0xb052 -#define MSG_ROUTE_POINT_IN 0xb056 -#define MSG_ROUTE_POINT_OUT 0xb053 -#define MSG_ROUTE_SHAPE_IN 0xb057 -#define MSG_ROUTE_SHAPE_OUT 0xb054 -#define MSG_SATELLITE_INFO 0xa020 -#define MSG_TRACK_COUNT 0xb030 -#define MSG_TRACK_HEADER_IN 0xb035 -#define MSG_TRACK_HEADER_OUT 0xb032 -#define MSG_TRACK_POINT_IN 0xb036 -#define MSG_TRACK_POINT_OUT 0xb033 -#define MSG_TRANSFER_COMPLETE 0xaa04 -#define MSG_VERSION 0xa001 -#define MSG_WAYPOINT_COUNT 0xb010 -#define MSG_WAYPOINT_IN 0xb014 -#define MSG_WAYPOINT_OUT 0xb013 -// Undocumented: -// This one looks like MSG_ACK, except it also has a string in it that says -// something like "device is busy". The expected MSG_ACK usually immediately -// follows it, so the point of this one is unclear. -#define MSG_NACK 0xaa01 -// Long waypoint notes -#define MSG_WAYPOINT_NOTE_IN 0xb016 -#define MSG_WAYPOINT_NOTE_OUT 0xb015 - -//----------------------------------------------------------------------------- -// Message structures - -// Input Delete Message -// Message ID: 0xB005 -typedef enum { - nuke_type_wpt = 0, - nuke_type_trk = 1, - nuke_type_rte = 2, - // int nuke_map = 3; -} nuke_type; - -typedef enum { - nuke_mode_all = 0, - nuke_mode_single = 1 -} nuke_mode; - -typedef enum { - nuke_dest_internal = 0, - nuke_dest_sd = 1 -} nuke_dest; - -typedef struct { - uint8_t type; - uint8_t mode; - uint8_t location; - char object_name[64]; -} msg_delete_t; - -// Output Waypoint Message -// Message ID: 0xB013 -// Input Waypoint Message -// Message ID: 0xB014 -typedef struct { - uint8_t total[4]; // U32 - uint8_t index[4]; // U32 - uint8_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t latitude[4]; // S32 rad * 100000000 - uint8_t longitude[4]; // S32 rad * 100000000 - uint8_t elevation[4]; // F32 meters - uint8_t color; - uint8_t symbol; - uint8_t name_size; - char name[1]; - // note_size[2] U16 - // note[note_size] -} msg_waypoint_t; - -// undocumented, seen with PN-40 2.5 firmware -// output waypoint note -// Message ID: 0xB015 -// input waypoint note -// Message ID: 0xB016 -typedef struct { - uint8_t index[2]; - uint8_t total[2]; - uint8_t name_size; - char name[1]; - // note_size[2] - // note[note_size] -} msg_waypoint_note_t; - -// Output Track Point Message -// Message ID: 0xB033 -// Input Track Point Message -// Message ID: 0xB036 -typedef struct { - uint8_t total[4]; // U32 - uint8_t index[4]; // U32 - uint8_t number; - struct { - uint8_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t latitude[4]; // S32 rad * 100000000 - uint8_t longitude[4]; // S32 rad * 100000000 - uint8_t elevation[4]; // F32 meters - uint8_t speed[2]; // U16 km/h * 10 - uint8_t heading[2]; // U16 deg * 100 - uint8_t status; - } point[1]; -} msg_track_point_t; - -// Output Track Header (Name) Message -// Message ID: 0xB032 -typedef struct { - uint8_t total_tracks[2]; // U16 - uint8_t number[2]; // U16 - char name[32]; - uint8_t total_points[4]; // U32 - uint8_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t color[2]; // U16 - uint8_t distance[4]; // U32 m - uint8_t duration[4]; // U32 sec - uint8_t comment_size[2]; // U16 - char comment[1]; -} msg_track_header_t; - -// Input Upload Track Header Message -// Message ID: 0xB035 -typedef struct { - char name[32]; - uint8_t total_points[4]; // U32 - uint8_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t color[2]; // U16 - uint8_t comment_size[2]; // U16 - char comment[1]; -} msg_track_header_in_t; - -// Output Route Shape Message -// Message ID: 0xB054 -typedef struct { - uint8_t total[4]; // U32 - uint8_t index[4]; // U32 - uint8_t number; - uint8_t reserved; - struct { - uint8_t latitude[4]; // S32 rad * 100000000 - uint8_t longitude[4]; // S32 rad * 100000000 - } point[1]; -} msg_route_shape_t; - -// Output Route Point Message -// Message ID: 0xB053 -// Input Route Itin Point Message -// Message ID: 0xB056 -typedef struct { - uint8_t total[4]; // U32 - uint8_t index[4]; // U32 - char name[32]; - uint8_t latitude[4]; // S32 rad * 100000000 - uint8_t longitude[4]; // S32 rad * 100000000 - uint8_t time_from_start[4]; // U32 sec - uint8_t distance_from_start[4]; // F32 km - uint8_t bearing_in[2]; // U16 deg * 100 - uint8_t bearing_out[2]; // U16 deg * 100 - uint8_t bearing_next[2]; // U16 deg * 100 - uint8_t itinerary_type; - uint8_t turn_type; - uint8_t road_class[2]; // U16 - uint8_t feature_code[4]; // U32 - uint8_t exit_label_size; - char exit_label[1]; - // comment_size U8 - // comment[comment_size] - // shape_pt_count U32 -} msg_route_point_t; - -// Output Route Header (Name) Message -// Message ID: 0xB052 -typedef struct { - uint8_t total[2]; // U16 - uint8_t index[2]; // U16 - char name[64]; - uint8_t type; - uint8_t total_route_point[4]; // U32 - uint8_t total_shape_point[4]; // U32 -} msg_route_header_t; - -// Input Upload Route Header Message -// Message ID: 0xB055 -typedef struct { - char name[64]; - uint8_t type; - uint8_t total_route_point[4]; // U32 - uint8_t total_shape_point[4]; // U32 -} msg_route_header_in_t; - -// Output Navigation Message -// Message ID: 0xA010 -typedef struct { - uint8_t gps_week[2]; // U16 - uint8_t time_of_week[8]; // D64 sec - uint8_t year[2]; // U16 - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; - uint8_t satellites; - uint8_t latitude[8]; // D64 deg - uint8_t longitude[8]; // D64 deg - uint8_t elevation[8]; // D64 meters - uint8_t geoid_offset[2]; // S16 meters * 10 - uint8_t speed[4]; // F32 km/h - uint8_t heading[2]; // U16 deg * 100 - uint8_t magnetic_variation[2]; // S16 deg * 100 - uint8_t fix_status; -} msg_navigation_t; - -// Output Satellite Info Message -// Message ID: 0xA020 -typedef struct { - uint8_t gps_week[2]; // U16 - uint8_t time_of_week[8]; // D64 sec - uint8_t hdop[2]; // U16 - uint8_t vdop[2]; // U16 - uint8_t pdop[2]; // U16 - uint8_t number; - struct { - uint8_t prn; - uint8_t azimuth[2]; // S16 deg? * 100 - uint8_t elevation[2]; // S16 deg? * 100 - uint8_t Cn0[2]; // U16 snr * 100 - uint8_t status; - } sat[1]; -} msg_satellite_t; - -// Output Version Message -// Message ID: 0xA001 -typedef struct { - uint8_t firmware_version[4]; - char company[32]; - char product[32]; - char firmware[32]; - char gps_firmware[48]; - char serial[16]; - char extra[16]; -} msg_version_t; - -// Output Device Capabilities Message -// Message ID: 0xB001 -typedef struct { - uint8_t max_waypoints[4]; // U32 - uint8_t max_tracks[2]; // U16 - uint8_t max_track_points[4]; // U32 - uint8_t max_routes[2]; // U16 - uint8_t max_route_points[4]; // U32 - uint8_t max_route_shape_points[4]; // U32 - uint8_t max_maps[2]; // U16 - uint8_t min_map_version[2]; // U16 - uint8_t max_map_version[2]; // U16 - uint8_t total_internal_file_memory[4]; // U32 - uint8_t avail_internal_file_memory[4]; // U32 - uint8_t total_external_file_memory[4]; // U32 - uint8_t avail_external_file_memory[4]; // U32 -} msg_capabilities_t; - -//----------------------------------------------------------------------------- - -#if __APPLE__ || __linux -#include -#endif - -static void -debug_out(const char* fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - fputs(MYNAME ": ", stderr); - vfprintf(stderr, fmt, ap); - va_end(ap); -} - -static void -debug_out_time(const char* s) -{ -#if __APPLE__ || __linux - struct timeval tv; - gettimeofday(&tv, NULL); - debug_out("%u.%03u %s", (unsigned)tv.tv_sec & 0xf, (unsigned)tv.tv_usec / 1000, s); -#else - debug_out("%u %s", (unsigned)time(NULL) & 0xf, s); -#endif -} - -//----------------------------------------------------------------------------- - -static uint16_t -checksum(const uint8_t* p, unsigned n) -{ - int x = 0; - unsigned i; - for (i = n / 2; i > 0; i--) { - x += *p++; - x += *p++ << 8; - } - if (n & 1) { - x += *p; - } - return (uint16_t)-x; -} - -//----------------------------------------------------------------------------- -// OS packet read/write wrappers - -static unsigned -packet_read(void* buf) -{ - unsigned n = delbin_os_ops.packet_read(buf); - if (n == 0) { - fatal(MYNAME ": read 0\n"); - } - if (global_opts.debug_level >= DBGLVL_H) { - unsigned j; - const uint8_t* p = (const uint8_t*) buf; - - debug_out_time("pcktrd"); - for (j = 0; j < n; j++) { - warning(" %02x", p[j]); - } - if (global_opts.debug_level >= DBGLVL_H2) { - warning(" "); - for (j = 0; j < n; j++) { - int c = p[j]; - warning("%c", isprint(c) ? c : '.'); - } - } - warning("\n"); - } - return n; -} - -static void -packet_write(const void* buf, unsigned size) -{ - unsigned n; - if (global_opts.debug_level >= DBGLVL_H) { - unsigned j; - const uint8_t* p = (const uint8_t*) buf; - - debug_out_time("pcktwr"); - for (j = 0; j < size; j++) { - warning(" %02x", p[j]); - } - if (global_opts.debug_level >= DBGLVL_H2) { - warning(" "); - for (j = 0; j < size; j++) { - int c = p[j]; - warning("%c", isprint(c) ? c : '.'); - } - } - warning("\n"); - } - n = delbin_os_ops.packet_write(buf, size); - if (n != size) { - fatal(MYNAME ": short write %u %u\n", size, n); - } -} - -//----------------------------------------------------------------------------- - -// dynamically sized buffer with space reserved for message header and trailer -typedef struct { - // message data size - unsigned size; - // buffer size - unsigned capacity; - uint8_t* buf; - // convenience pointer to message data area - void* data; -} message_t; - -static void -message_init(message_t* m) -{ - m->capacity = 100; - m->buf = (uint8_t*)xmalloc(m->capacity); - m->data = m->buf + 2 + 8; -} - -static void -message_init_size(message_t* m, unsigned size) -{ - m->size = size; - m->capacity = 2 + 8 + size + 4; - m->buf = (uint8_t*)xmalloc(m->capacity); - m->data = m->buf + 2 + 8; -} - -static void -message_free(message_t* m) -{ - xfree(m->buf); - m->buf = NULL; - m->data = NULL; -} - -static void -message_ensure_size(message_t* m, unsigned size) -{ - m->size = size; - if (m->capacity < 2 + 8 + size + 4) { - m->capacity = 2 + 8 + size + 4; - xfree(m->buf); - m->buf = (uint8_t*)xmalloc(m->capacity); - m->data = m->buf + 2 + 8; - } -} - -static unsigned -message_get_id(const message_t* m) -{ - return le_readu16(m->buf + 4); -} - -//----------------------------------------------------------------------------- - -static void -message_write(unsigned msg_id, message_t* m) -{ - unsigned chksum; - unsigned count; - unsigned n; - uint8_t* p = m->buf; - - // header (2 start bytes filled in later) - p[2] = 0xdb; - p[3] = 0xfe; - le_write16(p + 4, msg_id); - // "data size" includes 4 trailer bytes - le_write16(p + 6, m->size + 4); - chksum = checksum(p + 2, 6); - le_write16(p + 8, chksum); - // message data (filled in by caller) - chksum = checksum((uint8_t*) m->data, m->size); - n = 2 + 8 + m->size; - // trailer (checksum and marker bytes) - le_write16(p + n, chksum); - p[n + 2] = 0xad; - p[n + 3] = 0xbc; - // size of message not counting packet start bytes - count = 8 + m->size + 4; - do { - const uint8_t save0 = p[0]; - const uint8_t save1 = p[1]; - n = delbin_os_packet_size - 2; - if (n > count) { - n = count; - } - // doc. says 0x20, device sends 0, probably ignored - p[0] = 0x20; - // valid bytes in packet after first 2 - p[1] = n; - packet_write(p, 2 + n); - p[0] = save0; - p[1] = save1; - p += n; - count -= n; - } while (count != 0); - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": sent %x\n", msg_id); - } -} - -// read from the payload of a single packet -static unsigned -read_depacketize_1(uint8_t** p, unsigned n, int new_packet) -{ - static uint8_t buf[256]; - static unsigned buf_i, buf_n; - if (new_packet) { - buf_n = 0; - } - while (buf_n == 0) { - packet_read(buf); - if (buf[1] <= delbin_os_packet_size - 2) { - buf_n = buf[1]; - buf_i = 2; - } - } - *p = buf + buf_i; - if (n > buf_n) { - n = buf_n; - } - buf_n -= n; - buf_i += n; - return n; -} - -// read from packet payloads until request is fulfilled -static void -read_depacketize(uint8_t* buf, unsigned n) -{ - while (n) { - uint8_t* p; - unsigned nn = read_depacketize_1(&p, n, FALSE); - memcpy(buf, p, nn); - n -= nn; - buf += nn; - } -} - -// Get one valid message. -// If a corrupted message with the right id is seen, return failure (0). -static unsigned -message_read_1(unsigned msg_id, message_t* m) -{ - unsigned id; - for (;;) { - unsigned total; - unsigned n; - uint8_t buf[8]; - uint8_t* p; - - n = read_depacketize_1(&p, 8, FALSE); - memset(buf, 0, 8); - memcpy(buf, p, n); - while (buf[0] != 0xdb || buf[1] != 0xfe || checksum(buf, 6) != le_readu16(buf + 6)) { - // try for a message start at the beginning of next packet - n = read_depacketize_1(&p, 8, TRUE); - memset(buf, 0, 8); - memcpy(buf, p, n); - } - id = le_readu16(buf + 2); - total = le_readu16(buf + 4); - message_ensure_size(m, total - 4); - // copy in message head, really only need id field, do the rest for debugging - m->buf[0] = m->buf[1] = 0; - memcpy(m->buf + 2, buf, 8); - // read message body and trailer - read_depacketize((uint8_t*) m->data, total); - p = (uint8_t*)m->data + m->size; - if (checksum((uint8_t*) m->data, m->size) == le_readu16(p) && - p[2] == 0xad && p[3] == 0xbc) { - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": received %x\n", id); - } - break; - } - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": corrupted message %x\n", id); - } - if (id == msg_id) { - id = 0; - break; - } - } - return id; -} - -// Send MSG_ACK for given message -static void -message_ack(unsigned id, const message_t* m) -{ - message_t ack; - char* p1; - const char* p2 = (const char*) m->data; - switch (id) { - case MSG_ACK: - case MSG_NACK: - case MSG_NAVIGATION: - case MSG_SATELLITE_INFO: - // don't ack these - return; - } - message_init_size(&ack, 4); - p1 = (char*) ack.data; - // ack payload is id and body checksum of acked message - le_write16(p1, id); - p1[2] = p2[m->size]; - p1[3] = p2[m->size + 1]; - message_write(MSG_ACK, &ack); - message_free(&ack); -} - -// Get specific message, ignoring others. Sends ACK for non-interval messages. -// Gives up after at least READ_TIMEOUT-1 seconds have passed. -static int -message_read(unsigned msg_id, message_t* m) -{ - unsigned id; - time_t time_start = time(NULL); - - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": looking for %x\n", msg_id); - } - for (;;) { - id = message_read_1(msg_id, m); - if (id == 0) { - break; - } - if (id == MSG_ERROR) { - const uint8_t* p = (const uint8_t*) m->data; - fatal(MYNAME ": device error %u: \"%s\"\n", *p, p + 1); - } - message_ack(id, m); - if (id == msg_id || time(NULL) - time_start >= READ_TIMEOUT) { - break; - } - } - return id == msg_id; -} - -// Read a sequence of messages, up to a MSG_TRANSFER_COMPLETE -static int -get_batch(message_t** array, unsigned* n) -{ - int success = 1; - unsigned array_max = 100; - message_t* a = (message_t*) xmalloc(array_max * sizeof(message_t)); - unsigned i = 0; - unsigned id; - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": begin get_batch\n"); - } - do { - time_t time_start = time(NULL); - if (i == array_max) { - message_t* old_a = a; - array_max += array_max; - a = (message_t*) xmalloc(array_max * sizeof(message_t)); - memcpy(a, old_a, i * sizeof(message_t)); - xfree(old_a); - } - message_init(&a[i]); - for (;;) { - id = message_read_1(0, &a[i]); - switch (id) { - case MSG_NAVIGATION: - if (time(NULL) - time_start >= READ_TIMEOUT) { - success = 0; - break; - } - // fall through - case MSG_ACK: - case MSG_NACK: - case MSG_SATELLITE_INFO: - continue; - } - break; - } - message_ack(id, &a[i]); - i++; - } while (success && id != MSG_TRANSFER_COMPLETE); - if (success) { - *array = a; - *n = i - 1; - message_free(&a[*n]); - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": end get_batch, %u messages\n", *n); - } - } else { - while (i--) { - message_free(&a[i]); - } - xfree(a); - *array = NULL; - *n = 0; - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": end get_batch, failed\n"); - } - } - return success; -} - -typedef struct { - unsigned msg_id; - message_t msg; -} batch_array_t; - -static batch_array_t* batch_array; - -static unsigned batch_array_max; -static unsigned batch_array_i; - -// add a message to sequence that will later be sent all at once -static void -add_to_batch(unsigned id, message_t* m) -{ - if (batch_array_i == batch_array_max) { - char* old = (char*) batch_array; - if (batch_array_max == 0) { - batch_array_max = 50; - } - batch_array_max += batch_array_max; - batch_array = (batch_array_t*) xmalloc(batch_array_max * sizeof(*batch_array)); - if (batch_array_i) { - memcpy(batch_array, old, batch_array_i * sizeof(*batch_array)); - xfree(old); - } - } - batch_array[batch_array_i].msg_id = id; - batch_array[batch_array_i].msg = *m; - batch_array_i++; - memset(m, 0, sizeof(*m)); -} - -// send an accumulated sequence of messages -static void -send_batch(void) -{ - message_t m; - const unsigned n = batch_array_i; - unsigned i; - unsigned progress = 0; - - message_init(&m); - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": begin send_batch, %u messages\n", n); - } - for (i = 0; i < n; i++) { - unsigned timeout_count = 0; - time_t time_start = time(NULL); - - // Can't really trigger this off either i or n as we don't - // know how the various packets map to actual waypts. - if (global_opts.verbose_status && - (batch_array[i].msg_id == MSG_WAYPOINT_IN)) { - waypt_status_disp(waypoint_n, ++progress); - } - - message_write(batch_array[i].msg_id, &batch_array[i].msg); - for (;;) { - unsigned id = message_read_1(0, &m); - switch (id) { - case MSG_ACK: - break; - case MSG_NAVIGATION: - if (time(NULL) - time_start >= 2) { - if (timeout_count) { - fatal(MYNAME ": send_batch timed out\n"); - } - timeout_count++; - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": re-sending %x\n", batch_array[i].msg_id); - } - message_write(batch_array[i].msg_id, &batch_array[i].msg); - time_start = time(NULL); - } - // fall through - case MSG_NACK: - case MSG_SATELLITE_INFO: - continue; - default: - warning(MYNAME ": unexpected response message %x during send_batch\n", id); - continue; - } - break; - } - } - message_read(MSG_TRANSFER_COMPLETE, &m); - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": end send_batch\n"); - } - for (i = n; i--;) { - message_free(&batch_array[i].msg); - } - xfree(batch_array); - message_free(&m); - batch_array_i = batch_array_max = 0; -} - -//----------------------------------------------------------------------------- -// Coordinate conversion - -static double -delbin_rad2deg(int32_t x) -{ - return x * ((180 / M_PI) / 100000000); -} - -static int32_t -delbin_deg2rad(double x) -{ - return (int32_t)(x * ((M_PI / 180) * 100000000)); -} - -//----------------------------------------------------------------------------- -// Waypoint reading - -static time_t -decode_time(const uint8_t* p) -{ - struct tm t; - t.tm_year = p[0]; - t.tm_mon = p[1] - 1; - t.tm_mday = p[2]; - t.tm_hour = p[3]; - t.tm_min = p[4]; - t.tm_sec = p[5]; - return mkgmtime(&t); -} - -static Waypoint* -decode_waypoint(const void* data) -{ - Waypoint* wp = new Waypoint; - const msg_waypoint_t* p = (const msg_waypoint_t*)data; - const char* s; - float f; - - wp->SetCreationTime(decode_time(&p->year)); - wp->latitude = delbin_rad2deg(le_read32(p->latitude)); - wp->longitude = delbin_rad2deg(le_read32(p->longitude)); - f = le_read_float(p->elevation); - if (f > UNKNOWN_ELEV) { - wp->altitude = f; - } - wp->icon_descr = waypoint_symbol(p->symbol); -// if (!wp->icon_descr.isNull()) { -// wp->icon_descr = wp->icon_descr; -// } - if (p->name_size && p->name[0]) { - wp->description = p->name; - } - s = p->name + p->name_size; - if (le_readu16(s) && s[2]) { - wp->notes = xstrdup(s + 2); - } - return wp; -} - -static void -read_waypoints(void) -{ - message_t m; - message_t* msg_array; - unsigned msg_array_n; - Waypoint* wp = NULL; - unsigned n_point; - unsigned notes_i = 0; - unsigned notes_max = 0; - unsigned i; - int attempt = ATTEMPT_MAX; - - message_init(&m); - // get number of waypoints - for (;;) { - m.size = 0; - message_write(MSG_WAYPOINT_COUNT, &m); - if (message_read(MSG_WAYPOINT_COUNT, &m)) { - break; - } - if (--attempt == 0) { - fatal(MYNAME ": reading waypoint count failed\n"); - } - } - n_point = le_readu32(m.data); - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": %u waypoints\n", n_point); - } - if (n_point == 0) { - message_free(&m); - return; - } - // get waypoint messages - attempt = ATTEMPT_MAX; - for (;;) { - m.size = MSG_REQUEST_WAYPOINTS_SIZE; - memset(m.data, 0, m.size); - // This byte is documented as reserved. Setting it to 3 is required to get - // extended notes (message 0xb015) with PN-40 firmware 2.5. - // Whether it has any effect with earlier firmware or the PN-20 is unknown. - ((char*)m.data)[1] = 3; - message_write(MSG_REQUEST_WAYPOINTS, &m); - if (get_batch(&msg_array, &msg_array_n)) { - break; - } - if (--attempt == 0) { - fatal(MYNAME ": reading waypoints failed\n"); - } - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": timed out reading waypoints, retrying\n"); - } - m.size = MSG_BREAK_SIZE; - memset(m.data, 0, m.size); - message_write(MSG_BREAK, &m); - } - message_free(&m); - // process waypoint messages - for (i = 0; i < msg_array_n; i++) { - unsigned id = message_get_id(&msg_array[i]); - if (id == MSG_WAYPOINT_OUT) { - wp = decode_waypoint(msg_array[i].data); - waypt_add(wp); - notes_i = 0; - notes_max = 0; - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": read waypoint '%s'\n", qPrintable(wp->description)); - } - } else if (wp && id == MSG_WAYPOINT_NOTE_OUT) { - const msg_waypoint_note_t* p = (const msg_waypoint_note_t*) msg_array[i].data; - const char* s = p->name + p->name_size; - unsigned nn = le_readu16(s); - if (notes_max < notes_i + nn) { -#if NEW_STRINGS -// This section needs a serious rethinking. -#else - char* old = wp->notes; -#endif - if (notes_max == 0) { - notes_max = nn; - } - do { - notes_max += notes_max; - } while (notes_max < notes_i + nn); - wp->notes = (char*) xmalloc(notes_max); -#if NEW_STRINGS -#else - if (old) { - memcpy(wp->notes, old, notes_i); - xfree(old); - } -#endif - } - if (nn) { -#if NEW_STRINGS - // Is this really what this code was trying to do? - wp->notes += QString::fromUtf8(s + 2, nn); -#else - memcpy(wp->notes + notes_i, s + 2, nn); -#endif - notes_i += nn; - if (wp->notes[notes_i - 1] == 0) { - notes_i--; - } - } - } else { - fatal(MYNAME ": unexpected message %x while reading waypoints\n", id); - } - message_free(&msg_array[i]); - } - xfree(msg_array); -} - -//----------------------------------------------------------------------------- -// Waypoint writing - -static void -encode_time(time_t time_, uint8_t* p) -{ - const struct tm* t = gmtime(&time_); - p[0] = t->tm_year; - p[1] = t->tm_mon + 1; - p[2] = t->tm_mday; - p[3] = t->tm_hour; - p[4] = t->tm_min; - p[5] = t->tm_sec; -} - -static void -get_gc_notes(const Waypoint* wp, int* symbol, char** notes, unsigned* notes_size) -{ - fs_xml* fs_gpx; - xml_tag* root = NULL; - gbfile* fd = gbfopen(NULL, "w", MYNAME); - const char* size = NULL; - int gc_sym = 0; - - switch (wp->gc_data->type) { - case gt_traditional: - gc_sym = 160; - break; - case gt_multi: - gc_sym = 161; - break; - case gt_virtual: - gc_sym = 169; - break; - case gt_letterbox: - gc_sym = 163; - break; - case gt_event: - gc_sym = 165; - break; - case gt_suprise: - gc_sym = 162; - break; - case gt_webcam: - gc_sym = 170; - break; - case gt_earth: - gc_sym = 168; - break; - case gt_benchmark: - gc_sym = 172; - break; - case gt_cito: - gc_sym = 167; - break; - case gt_mega: - gc_sym = 166; - break; - case gt_wherigo: - gc_sym = 164; - break; - case gt_unknown: - case gt_locationless: - case gt_ape: - break; - } - if (0 == (wp->icon_descr.compare("Geocache Found"))) { - gc_sym = 124; - } - if (!wp->description.isEmpty()) { - gbfputs(wp->description, fd); - if (!wp->gc_data->placer.isEmpty()) { - gbfprintf(fd, " by %s", CSTR(wp->gc_data->placer)); - } - gbfputc('\n', fd); - } - - gbfprintf(fd, "Cache ID: %s\n", CSTRc(wp->shortname)); - if (gc_sym && opt_gcsym && atoi(opt_gcsym)) { - gbfprintf(fd, "%s\n", waypoint_symbol(gc_sym)); - *symbol = gc_sym; - } else if (!wp->icon_descr.isNull()) { - gbfprintf(fd, "%s\n", CSTR(wp->icon_descr)); - } - switch (wp->gc_data->container) { - case gc_micro: - size = "Micro"; - break; - case gc_small: - size = "Small"; - break; - case gc_regular: - size = "Regular"; - break; - case gc_large: - size = "Large"; - break; - case gc_unknown: - size = "Not Chosen" ; - break; - case gc_other: - size = "Other"; - break; - // Device has no symbol for this, but this is what Topo sends. - case gc_virtual: - size = "Virtual"; - break; - default: - break; - } - if (size) { - gbfprintf(fd, "SIZE: %s\n", size); - } - if (wp->gc_data->diff % 10) { - gbfprintf(fd, "D%.1f", wp->gc_data->diff / 10.0); - } else { - gbfprintf(fd, "D%u", wp->gc_data->diff / 10); - } - if (wp->gc_data->terr % 10) { - gbfprintf(fd, "/T%.1f\n", wp->gc_data->terr / 10.0); - } else { - gbfprintf(fd, "/T%u\n", wp->gc_data->terr / 10); - } - if (!wp->gc_data->hint.isEmpty() && !opt_hint_at_end) { - gbfprintf(fd, "HINT: %s\n", CSTR(wp->gc_data->hint)); - } - if (!wp->gc_data->desc_short.utfstring.isEmpty() || !wp->gc_data->desc_long.utfstring.isEmpty()) { - gbfputs("DESC: ", fd); - if (!wp->gc_data->desc_short.utfstring.isEmpty()) { - char* s1 = strip_html(&wp->gc_data->desc_short); - char* s2 = cet_str_utf8_to_any(s1, global_opts.charset); - gbfprintf(fd, "%s\n", s2); - xfree(s2); - xfree(s1); - } - if (!wp->gc_data->desc_long.utfstring.isEmpty()) { - char* s1 = strip_html(&wp->gc_data->desc_long); - char* s2 = cet_str_utf8_to_any(s1, global_opts.charset); - gbfputs(s2, fd); - xfree(s2); - xfree(s1); - } - } - fs_gpx = (fs_xml*)fs_chain_find(wp->fs, FS_GPX); - if (opt_logs && fs_gpx && fs_gpx->tag) { - root = xml_findfirst(fs_gpx->tag, "groundspeak:logs"); - } - if (root) { - xml_tag* curlog = xml_findfirst(root, "groundspeak:log"); - if (curlog) { - gbfputs("\nLOG:\n", fd); - } - for (; curlog; curlog = xml_findnext(root, curlog, "groundspeak:log")) { - xml_tag* logpart = xml_findfirst(curlog, "groundspeak:type"); - if (logpart) { - gbfprintf(fd, "%s\n", CSTR(logpart->cdata)); - } - logpart = xml_findfirst(curlog, "groundspeak:date"); - if (logpart) { - time_t logtime = xml_parse_time(logpart->cdata).toTime_t(); - const struct tm* logtm = gmtime(&logtime); - gbfprintf(fd, "%d-%02d-%02d ", logtm->tm_year + 1900, logtm->tm_mon + 1, logtm->tm_mday); - } - logpart = xml_findfirst(curlog, "groundspeak:finder"); - if (logpart) { - char* s = cet_str_utf8_to_any(CSTR(logpart->cdata), global_opts.charset); - gbfputs(s, fd); - xfree(s); - } - logpart = xml_findfirst(curlog, "groundspeak:text"); - if (logpart) { - char* s = cet_str_utf8_to_any(CSTR(logpart->cdata), global_opts.charset); - gbfprintf(fd, ", %s", s); - xfree(s); - } - gbfputc('\n', fd); - } - } - if (!wp->gc_data->hint.isEmpty() && opt_hint_at_end) { - gbfprintf(fd, "\nHINT: %s\n", CSTR(wp->gc_data->hint)); - } - gbfputc(0, fd); - *notes_size = fd->memlen; - *notes = (char*) xmalloc(*notes_size); - memcpy(*notes, fd->handle.mem, *notes_size); - gbfclose(fd); -} - -static void -write_waypoint_notes(const char* notes, unsigned size, const char* name) -{ - message_t m; - const unsigned name_size = strlen(name) + 1; - const unsigned bytes_per_msg = (10 * (delbin_os_packet_size - 2)) - name_size - 20; - const unsigned msg_count = (size + (bytes_per_msg - 1)) / bytes_per_msg; - unsigned i = 1; - - do { - char* pp; - unsigned n = bytes_per_msg; - msg_waypoint_note_t* p; - message_init_size(&m, 2 + 2 + 1 + name_size + 2 + bytes_per_msg); - p = (msg_waypoint_note_t*) m.data; - le_write16(p->index, i++); - le_write16(p->total, msg_count); - p->name_size = name_size; - memcpy(p->name, name, p->name_size); - pp = p->name + p->name_size; - if (n > size) { - n = size; - } - le_write16(pp, n); - pp += 2; - memcpy(pp, notes, n); - pp += n; - if (*(pp - 1)) { - *pp++ = 0; - } - notes += n; - size -= n; - m.size = pp - (char*)p; - add_to_batch(MSG_WAYPOINT_NOTE_IN, &m); - } while (size != 0); -} - -static void -add_nuke(nuke_type type) -{ - message_t m; - msg_delete_t* p; - - message_init_size(&m, MSG_DELETE_SIZE); - p = (msg_delete_t*) m.data; - p->type = type; - p->mode = nuke_mode_all; - p->location = nuke_dest_internal; - memset(p->object_name, 0, sizeof(p->object_name)); - - // MSG_DELETE generates a MSG_TRANSFER_COMPLETE, - // so use the batch facility to wait for it - add_to_batch(MSG_DELETE, &m); - send_batch(); -} - -static void -write_waypoint(const Waypoint* wp) -{ - message_t m; - msg_waypoint_t* p; - QString name = wp->shortname; - char* notes; - unsigned name_size; - unsigned notes_size = 0; - unsigned extended_notes_size = 0; - const char* notes_freeable = NULL; - int symbol = -1; - float elev = UNKNOWN_ELEV; - char* pp; - - if (wp->EmptyGCData()) { - notes = xstrdup(wp->notes); - if (notes == NULL && wp->description.isEmpty() && wp->shortname != wp->description) { - notes = xstrdup(wp->description); - } - if (notes) { - notes_size = strlen(notes) + 1; - } - } else { - get_gc_notes(wp, &symbol, ¬es, ¬es_size); - notes_freeable = notes; - if (!wp->description.isEmpty()) { - name = mkshort(mkshort_handle, wp->description); - } - } - - if (notes_size > 800) { - if (use_extended_notes) { - extended_notes_size = notes_size; - notes_size = 1; - } else { - notes_size = 800; - } - } - - name_size = strlen(CSTRc(name)) + 1; - if (name_size > 255) { - name_size = 255; - } - message_init_size(&m, 31 + name_size + notes_size); - p = (msg_waypoint_t*) m.data; - - waypoint_i++; - le_write32(p->total, waypoint_n); - le_write32(p->index, waypoint_i); - encode_time(wp->GetCreationTime().toTime_t(), &p->year); - le_write32(p->latitude, delbin_deg2rad(wp->latitude)); - le_write32(p->longitude, delbin_deg2rad(wp->longitude)); - if (wp->altitude > unknown_alt) { - elev = wp->altitude; - } - le_write_float(p->elevation, elev); - if (symbol < 0) { - symbol = 0; - if (!wp->icon_descr.isNull()) { - symbol = waypoint_symbol_index(CSTR(wp->icon_descr)); - } - } - p->symbol = symbol; - p->name_size = name_size; - memcpy(p->name, CSTRc(name), name_size - 1); - p->name[name_size - 1] = 0; - pp = p->name + name_size; - m.size = (pp + 2 + notes_size) - (char*)p; - if (extended_notes_size) { - le_write16(pp, 0xffff); - pp[2] = 0; - } else { - le_write16(pp, notes_size); -#if NEW_STRINGS -#else - if (notes) { - memcpy(pp + 2, notes, notes_size - 1); - pp[2 + notes_size - 1] = 0; - } -#endif - } - - add_to_batch(MSG_WAYPOINT_IN, &m); - - if (extended_notes_size) { - write_waypoint_notes(notes, extended_notes_size, CSTRc(name)); - } - if (notes_freeable) { - xfree(notes_freeable); - } - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": wrote waypoint %u '%s'\n", waypoint_i, qPrintable(name)); - } -} - -static void -write_waypoints(void) -{ - message_t m; - unsigned device_n = 0; - - waypoint_i = 0; - waypoint_n = waypt_count(); - if (waypoint_n > device_max_waypoint) { - fatal(MYNAME ": waypoint count (%u) exceeds device limit (%u)\n", - waypoint_n, device_max_waypoint); - } - - message_init_size(&m, 0); - message_write(MSG_WAYPOINT_COUNT, &m); - if (message_read(MSG_WAYPOINT_COUNT, &m)) { - device_n = le_readu32(m.data); - } - - waypt_disp_all(write_waypoint); - send_batch(); - - if (device_n + waypoint_n > device_max_waypoint) { - m.size = 0; - message_write(MSG_WAYPOINT_COUNT, &m); - if (message_read(MSG_WAYPOINT_COUNT, &m) && - le_readu32(m.data) == device_max_waypoint) { - warning(MYNAME ": waypoint count (%u already on device + %u added = %u)" - " exceeds device limit (%u), some may have been discarded\n", - device_n, waypoint_n, device_n + waypoint_n, device_max_waypoint); - } - } - message_free(&m); -} - -//----------------------------------------------------------------------------- -// Track reading - -static void -decode_sat_fix(Waypoint* wp, const uint8_t status) -{ - switch (status & 3) { - case 1: - wp->fix = fix_none; - break; - case 2: - wp->fix = fix_2d; - break; - case 3: - wp->fix = fix_3d; - if (status & 4) { - wp->fix = fix_dgps; - } - break; - } -} - -static void -decode_track_point(const void* data, unsigned* wp_array_i, unsigned max_point) -{ - const msg_track_point_t* p = (const msg_track_point_t*) data; - const unsigned n = p->number; - unsigned i; - unsigned j = *wp_array_i; - - if (j + n > max_point) { - fatal(MYNAME ": read too many track points\n"); - } - for (i = 0; i < n; i++, j++) { - Waypoint* wp = new Waypoint; - float elev = le_read_float(p->point[i].elevation); - wp_array[j] = wp; - wp->SetCreationTime(decode_time(&p->point[i].year)); - wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude)); - wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude)); - if (elev > UNKNOWN_ELEV) { - wp->altitude = elev; - } - wp->speed = le_readu16(p->point[i].speed); - wp->speed *= (100.0f / (60 * 60)); - wp->wpt_flags.speed = 1; - decode_sat_fix(wp, p->point[i].status); - wp->wpt_flags.new_trkseg = (p->point[i].status & 0x10) != 0; - } - *wp_array_i = j; -} - -static void -read_track(route_head* track) -{ - message_t m; - message_t* msg_array; - const msg_track_header_t* p; - unsigned msg_array_n; - unsigned wp_array_i = 0; - unsigned n_point; - unsigned i; - int attempt = ATTEMPT_MAX; - - message_init(&m); - // read track messages - for (;;) { - m.size = MSG_REQUEST_TRACKS_SIZE; - memset(m.data, 0, m.size); - ((char*)m.data)[0] = 1; // Download single track - strcpy((char*)m.data + 1, CSTRc(track->rte_name)); - message_write(MSG_REQUEST_TRACKS, &m); - if (get_batch(&msg_array, &msg_array_n)) { - break; - } - if (--attempt == 0) { - fatal(MYNAME ": reading track '%s' failed\n", qPrintable(track->rte_name)); - } - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": timed out reading track '%s', retrying\n", qPrintable(track->rte_name)); - } - m.size = MSG_BREAK_SIZE; - memset(m.data, 0, m.size); - message_write(MSG_BREAK, &m); - } - message_free(&m); - if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_TRACK_HEADER_OUT) { - fatal(MYNAME ": reading track '%s' failed (missing track header)\n", qPrintable(track->rte_name)); - } - // process track messages - p = (const msg_track_header_t*) msg_array[0].data; - if (le_readu16(p->comment_size)) { - track->rte_desc = p->comment; - } - track->line_color.bbggrr = track_color(p->color[0]); - n_point = le_readu32(p->total_points); - wp_array = (Waypoint**) xcalloc(n_point, sizeof(*wp_array)); - message_free(&msg_array[0]); - for (i = 1; i < msg_array_n; i++) { - unsigned id = message_get_id(&msg_array[i]); - if (id == MSG_TRACK_POINT_OUT) { - decode_track_point(msg_array[i].data, &wp_array_i, n_point); - } else { - fatal(MYNAME ": unexpected message %x while reading track '%s'\n", id, qPrintable(track->rte_name)); - } - message_free(&msg_array[i]); - } - xfree(msg_array); - if (n_point != wp_array_i) { - fatal(MYNAME ": track point count mismatch, expected %u, got %u\n", n_point, wp_array_i); - } - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": read track '%s' %u points\n", qPrintable(track->rte_name), n_point); - } - for (i = 0; i < n_point; i++) { - track_add_wpt(track, wp_array[i]); - } - track_add_head(track); - xfree(wp_array); -} - -static void -read_tracks(void) -{ - message_t m; - message_t* msg_array; - unsigned msg_array_n; - route_head** track_array; - unsigned total; - unsigned i; - int attempt = ATTEMPT_MAX; - - message_init(&m); - // get number of tracks - for (;;) { - m.size = 0; - message_write(MSG_TRACK_COUNT, &m); - if (message_read(MSG_TRACK_COUNT, &m)) { - break; - } - if (--attempt == 0) { - fatal(MYNAME ": reading track count failed\n"); - } - } - total = le_readu32(m.data); - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": %u tracks\n", total); - } - if (total == 0) { - message_free(&m); - return; - } - - // First get track headers, then request each track with non-zero number of points - attempt = ATTEMPT_MAX; - for (;;) { - m.size = MSG_REQUEST_TRACKS_SIZE; - memset(m.data, 0, m.size); - ((char*)m.data)[0] = 2; // Download all track headers - message_write(MSG_REQUEST_TRACKS, &m); - if (get_batch(&msg_array, &msg_array_n)) { - break; - } - if (--attempt == 0) { - fatal(MYNAME ": reading track headers failed\n"); - } - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": timed out reading track headers, retrying\n"); - } - m.size = MSG_BREAK_SIZE; - memset(m.data, 0, m.size); - message_write(MSG_BREAK, &m); - } - message_free(&m); - track_array = (route_head**) xcalloc(total, sizeof(*track_array)); - for (i = 0; i < msg_array_n; i++) { - unsigned id = message_get_id(&msg_array[i]); - if (id == MSG_TRACK_HEADER_OUT) { - const msg_track_header_t* p = (msg_track_header_t*) msg_array[i].data; - if (le_readu32(p->total_points)) { - track_array[i] = route_head_alloc(); - track_array[i]->rte_name = p->name; - } - } else { - fatal(MYNAME ": unexpected message %x while reading track headers\n", id); - } - message_free(&msg_array[i]); - } - xfree(msg_array); - // get each track - for (i = 0; i < total; i++) { - if (track_array[i]) { - read_track(track_array[i]); - } - } - xfree(track_array); -} - -//----------------------------------------------------------------------------- -// Track writing - -static void -write_track_points(void) -{ - message_t m; - const unsigned pt_per_msg = 10; - msg_track_point_t* p = NULL; - unsigned i = 0; - unsigned j = 0; - - do { - const Waypoint* wp = wp_array[i]; - float f; - - if (j == 0) { - message_init_size(&m, 9 + 23 * pt_per_msg); - p =(msg_track_point_t*) m.data; - le_write32(p->total, waypoint_n); - le_write32(p->index, i + 1); - } - assert(p); - encode_time(wp->GetCreationTime().toTime_t(), &p->point[j].year); - le_write32(p->point[j].latitude, delbin_deg2rad(wp->latitude)); - le_write32(p->point[j].longitude, delbin_deg2rad(wp->longitude)); - f = UNKNOWN_ELEV; - if (wp->altitude > unknown_alt) { - f = wp->altitude; - } - le_write_float(p->point[j].elevation, f); - f = WAYPT_GET(wp, speed, 0); - f *= (60 * 60) / 100; - le_write16(p->point[j].speed, (uint16_t)f); - f = WAYPT_GET(wp, course, 0); - f *= 100; - le_write16(p->point[j].heading, (uint16_t)f); - switch (wp->fix) { - default: - p->point[j].status = 0; - break; - case fix_none: - p->point[j].status = 1; - break; - case fix_2d: - p->point[j].status = 2; - break; - case fix_3d: - p->point[j].status = 3; - break; - case fix_dgps: - p->point[j].status = 4 | 3; - break; - } - if (wp->wpt_flags.new_trkseg) { - p->point[j].status |= 0x10; - } - i++; - j++; - if (j == pt_per_msg || i == waypoint_n) { - p->number = j; - m.size = 9 + 23 * j; - add_to_batch(MSG_TRACK_POINT_IN, &m); - j = 0; - } - } while (i < waypoint_n); -} - -static void -write_track_begin(const route_head* track) -{ - waypoint_i = 0; - waypoint_n = track->rte_waypt_ct; - if (waypoint_n) { - wp_array = (Waypoint**) xmalloc(waypoint_n * sizeof(*wp_array)); - } -} - -static void -write_track_point(const Waypoint* wp) -{ - wp_array[waypoint_i++] = (Waypoint*)wp; -} - -static void -write_track_end(const route_head* track) -{ - message_t m; - msg_track_header_in_t* p; - unsigned comment_size = 0; - - if (waypoint_n == 0) { - return; - } - if (!track->rte_desc.isEmpty()) { - comment_size = strlen(CSTRc(track->rte_desc)) + 1; - } - message_init_size(&m, sizeof(msg_track_header_in_t) - 1 + comment_size); - p = (msg_track_header_in_t*) m.data; - memset(p->name, 0, sizeof(p->name)); - if (!track->rte_name.isEmpty()) { - strncpy(p->name, CSTRc(track->rte_name), sizeof(p->name) - 1); - } else { - sprintf(p->name, "%lu", (long)wp_array[0]->GetCreationTime().toTime_t()); - } - le_write32(p->total_points, waypoint_n); - encode_time(current_time().toTime_t(), &p->year); - le_write16(p->color, track_color_index(track->line_color.bbggrr)); - le_write16(p->comment_size, comment_size); - if (comment_size) { - memcpy(p->comment, CSTRc(track->rte_desc), comment_size); - } - add_to_batch(MSG_TRACK_HEADER_IN, &m); - write_track_points(); - send_batch(); - xfree(wp_array); -} - -static void -write_tracks(void) -{ - track_disp_all(write_track_begin, write_track_end, write_track_point); -} - -//----------------------------------------------------------------------------- -// Route reading - -static void -decode_route_shape(const void* data, unsigned* wp_array_i) -{ - const msg_route_shape_t* p = (msg_route_shape_t*) data; - const unsigned n = p->number; - unsigned i; - unsigned j = *wp_array_i; - - for (i = 0; i < n; i++, j++) { - char buf[32]; - Waypoint* wp = new Waypoint; - wp_array[j] = wp; - wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude)); - wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude)); - sprintf(buf, "SHP%03u", j); - wp->shortname = buf; - } - *wp_array_i = j; -} - -static Waypoint* -decode_route_point(const void* data) -{ - const msg_route_point_t* p = (const msg_route_point_t*) data; - const char* s = NULL; - gbfile* fd = gbfopen(NULL, "w", MYNAME); - Waypoint* wp = new Waypoint; - if (p->name[0]) { - wp->shortname = p->name; - } - // give these a higher priority than the shape points - wp->route_priority = 1; - wp->latitude = delbin_rad2deg(le_read32(p->latitude)); - wp->longitude = delbin_rad2deg(le_read32(p->longitude)); - switch (p->itinerary_type) { - case 1: - s = "Start"; - break; - case 2: - s = "Stop"; - break; - case 3: - s = "Finish"; - break; - case 4: - s = "Via"; - break; - case 5: - s = "Via Hidden"; - break; - case 6: - switch (p->turn_type) { - case 1: - s = "Turn, Straight"; - break; - case 2: - s = "Turn, Right"; - break; - case 3: - s = "Turn, Bear Right"; - break; - case 4: - s = "Turn, Keep Right"; - break; - case 5: - s = "Turn, Left"; - break; - case 6: - s = "Turn, Bear Left"; - break; - case 7: - s = "Turn, Keep Left"; - break; - case 8: - s = "Turn, Reverse Direction"; - break; - case 9: - s = "Turn, Street Name Change"; - break; - } - break; - } - if (s) { - gbfprintf(fd, "Type: %s", s); - } - if (p->exit_label_size && p->exit_label[0]) { - gbfprintf(fd, "\nExit: %s", p->exit_label); - } - s = p->exit_label + p->exit_label_size; - if (s[0] && s[1]) { - gbfprintf(fd, "\n%s", s + 1); - } - if (fd->memlen) { - gbfputc(0, fd); -#if NEW_STRINGS - // Reconsider if there's a less grubby way to do this. - wp->notes = QString::fromUtf8((const char*) fd->handle.mem, fd->memlen); -#else - wp->notes = (char*) xmalloc(fd->memlen); - memcpy(wp->notes, fd->handle.mem, fd->memlen); -#endif - } - gbfclose(fd); - return wp; -} - -static void -read_route(route_head* route) -{ - message_t m; - message_t* msg_array; - const msg_route_header_t* p; - unsigned msg_array_n; - unsigned wp_array_i = 0; - unsigned route_total, shape_total, total; - unsigned i; - int attempt = ATTEMPT_MAX; - - message_init(&m); - for (;;) { - m.size = MSG_REQUEST_ROUTES_SIZE; - memset(m.data, 0, m.size); - ((char*)m.data)[0] = 1; // Download single route - strcpy((char*)m.data + 1, CSTRc(route->rte_name)); - message_write(MSG_REQUEST_ROUTES, &m); - if (get_batch(&msg_array, &msg_array_n)) { - break; - } - if (--attempt == 0) { - fatal(MYNAME ": reading route '%s' failed (timed out)\n", qPrintable(route->rte_name)); - } - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": timed out reading route route '%s', retrying\n", qPrintable(route->rte_name)); - } - m.size = MSG_BREAK_SIZE; - memset(m.data, 0, m.size); - message_write(MSG_BREAK, &m); - } - message_free(&m); - if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_ROUTE_HEADER_OUT) { - fatal(MYNAME ": missing route header\n"); - } - p = (const msg_route_header_t*) msg_array[0].data; - route_total = le_readu32(p->total_route_point); - shape_total = le_readu32(p->total_shape_point); - total = route_total + shape_total; - wp_array = (Waypoint**) xcalloc(total, sizeof(*wp_array)); - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": route '%s' %u points, %u shape points\n", - qPrintable(route->rte_name), route_total, shape_total); - } - message_free(&msg_array[0]); - for (i = 1; i < msg_array_n; i++) { - unsigned id = message_get_id(&msg_array[i]); - if (id == MSG_ROUTE_POINT_OUT) { - wp_array[wp_array_i] = decode_route_point(msg_array[i].data); - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": route point '%s'\n", qPrintable(wp_array[wp_array_i]->shortname)); - } - wp_array_i++; - } else if (id == MSG_ROUTE_SHAPE_OUT) { - decode_route_shape(msg_array[i].data, &wp_array_i); - } else { - fatal(MYNAME ": unexpected message %x while reading route '%s'\n", id, qPrintable(route->rte_name)); - } - message_free(&msg_array[i]); - } - xfree(msg_array); - if (total != wp_array_i) { - fatal(MYNAME ": route point count mismatch, expected %u, got %u\n", total, wp_array_i); - } - for (i = 0; i < total; i++) { - route_add_wpt(route, wp_array[i]); - } - xfree(wp_array); - route_add_head(route); -} - -static void -read_routes(void) -{ - message_t m; - message_t* msg_array; - unsigned msg_array_n; - route_head** route_array; - unsigned total; - unsigned i; - int attempt = ATTEMPT_MAX; - - message_init(&m); - // get number of routes - for (;;) { - m.size = 0; - message_write(MSG_ROUTE_COUNT, &m); - if (message_read(MSG_ROUTE_COUNT, &m)) { - break; - } - if (--attempt == 0) { - fatal(MYNAME ": reading route count failed\n"); - } - } - total = le_readu32(m.data); - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": %u routes\n", total); - } - if (total == 0) { - message_free(&m); - return; - } - - // First get route headers, then request each route - attempt = ATTEMPT_MAX; - for (;;) { - m.size = MSG_REQUEST_ROUTES_SIZE; - memset(m.data, 0, m.size); - ((char*)m.data)[0] = 2; // Download all route headers - message_write(MSG_REQUEST_ROUTES, &m); - if (get_batch(&msg_array, &msg_array_n)) { - break; - } - if (--attempt == 0) { - fatal(MYNAME ": reading route headers failed\n"); - } - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": timed out reading route headers, retrying\n"); - } - m.size = MSG_BREAK_SIZE; - memset(m.data, 0, m.size); - message_write(MSG_BREAK, &m); - } - message_free(&m); - route_array = (route_head**) xcalloc(total, sizeof(*route_array)); - for (i = 0; i < msg_array_n; i++) { - unsigned id = message_get_id(&msg_array[i]); - if (id == MSG_ROUTE_HEADER_OUT) { - route_array[i] = route_head_alloc(); - route_array[i]->rte_name = ((msg_route_header_t*)msg_array[i].data)->name; - } else { - fatal(MYNAME ": unexpected message %x while reading route headers\n", id); - } - message_free(&msg_array[i]); - } - xfree(msg_array); - // get each route - for (i = 0; i < total; i++) { - read_route(route_array[i]); - } - xfree(route_array); -} - -//----------------------------------------------------------------------------- -// Route writing - -static unsigned route_point_n; -static unsigned shape_point_n; -static unsigned* shape_point_counts; - -static void -write_route_shape_points(Waypoint** array, unsigned n) -{ - message_t m; - const unsigned pt_per_msg = 25; - msg_route_shape_t* p = NULL; - unsigned i = 0; - unsigned j = 0; - - do { - if (j == 0) { - message_init_size(&m, 10 + 8 * pt_per_msg); - p = (msg_route_shape_t*) m.data; - le_write32(p->total, n); - le_write32(p->index, i + 1); - p->reserved = 0; - } - assert(p); - le_write32(p->point[j].latitude, delbin_deg2rad(array[i]->latitude)); - le_write32(p->point[j].longitude, delbin_deg2rad(array[i]->longitude)); - i++; - j++; - if (j == pt_per_msg || i == n) { - p->number = j; - m.size = 10 + 8 * j; - add_to_batch(MSG_ROUTE_SHAPE_IN, &m); - j = 0; - } - } while (i < n); -} - -static void -write_route_points(void) -{ - unsigned route_point_i = 0; - unsigned i = 0; - - while (i < waypoint_n) { - message_t m; - unsigned shape_n; - const Waypoint* wp = wp_array[i]; - msg_route_point_t* p; - char* s; - - message_init_size(&m, sizeof(msg_route_point_t) + 1 + 1 + 4); - p = (msg_route_point_t*) m.data; - memset(m.data, 0, m.size); - route_point_i++; - shape_n = shape_point_counts[route_point_i]; - le_write32(p->total, route_point_n); - le_write32(p->index, route_point_i); - if (!wp->shortname.isEmpty()) { - strncpy(p->name, CSTRc(wp->shortname), sizeof(p->name) - 1); - } else { - sprintf(p->name, "RPT%u", route_point_i); - } - le_write32(p->latitude, delbin_deg2rad(wp->latitude)); - le_write32(p->longitude, delbin_deg2rad(wp->longitude)); - p->exit_label_size = 1; - s = p->exit_label + p->exit_label_size; - s[0] = 1; // comment size - le_write32(s + 2, shape_n); - if (route_point_i == 1) { - p->itinerary_type = 1; // start - } else if (route_point_i == route_point_n) { - p->itinerary_type = 3; // finish - } - add_to_batch(MSG_ROUTE_POINT_IN, &m); - i++; - if (shape_n) { - write_route_shape_points(&wp_array[i], shape_n); - i += shape_n; - } - } -} - -static void -write_route_begin(const route_head* track) -{ - waypoint_i = 0; - route_point_n = 0; - shape_point_n = 0; - waypoint_n = track->rte_waypt_ct; - if (waypoint_n) { - wp_array = (Waypoint**) xmalloc(waypoint_n * sizeof(*wp_array)); - shape_point_counts = (unsigned int*) xcalloc(waypoint_n, sizeof(*shape_point_counts)); - } -} - -static void -write_route_point(const Waypoint* wp) -{ - wp_array[waypoint_i++] = (Waypoint*)wp; - if (wp->shortname.startsWith("SHP")) { - shape_point_n++; - shape_point_counts[route_point_n]++; - } else { - route_point_n++; - } -} - -static void -write_route_end(const route_head* route) -{ - message_t m; - msg_route_header_in_t* p; - - if (waypoint_n == 0) { - return; - } - message_init_size(&m, sizeof(msg_route_header_in_t)); - p = (msg_route_header_in_t*) m.data; - memset(p->name, 0, sizeof(p->name)); - if (!route->rte_name.isEmpty()) { - strncpy(p->name, CSTRc(route->rte_name), sizeof(p->name) - 1); - } else { - sprintf(p->name, "%lu", (long)wp_array[0]->GetCreationTime().toTime_t()); - } - p->type = 0; - le_write32(p->total_route_point, route_point_n); - le_write32(p->total_shape_point, shape_point_n); - add_to_batch(MSG_ROUTE_HEADER_IN, &m); - write_route_points(); - send_batch(); - if (wp_array) { - xfree(wp_array); - xfree(shape_point_counts); - } -} - -static void -write_routes(void) -{ - route_disp_all(write_route_begin, write_route_end, write_route_point); -} - -//----------------------------------------------------------------------------- -// Current position - -static Waypoint* -decode_navmsg(const void* data) -{ - Waypoint* wp = new Waypoint; - const msg_navigation_t* p = (const msg_navigation_t*) data; - struct tm t; - - t.tm_year = le_readu16(p->year) - 1900; - t.tm_mon = p->month - 1; - t.tm_mday = p->day; - t.tm_hour = p->hour; - t.tm_min = p->minute; - t.tm_sec = p->second; - wp->SetCreationTime(mkgmtime(&t)); - wp->sat = p->satellites; - wp->latitude = le_read_double(p->latitude); - wp->longitude = le_read_double(p->longitude); - wp->altitude = le_read_double(p->elevation); - wp->speed = le_read_float(p->speed); - wp->speed *= (1000.0f / (60 * 60)); - wp->wpt_flags.speed = 1; - wp->course = le_readu16(p->heading); - wp->course /= 100; - wp->wpt_flags.course = 1; - decode_sat_fix(wp, p->fix_status); - wp->shortname = "Position"; - return wp; -} - -static Waypoint* -read_position(void) -{ - Waypoint* wp; - message_t m; - - message_init(&m); - message_read(MSG_NAVIGATION, &m); - wp = decode_navmsg(m.data); - if (wp->fix > fix_none && - message_read_1(MSG_SATELLITE_INFO, &m) == MSG_SATELLITE_INFO) { - const msg_satellite_t* p = (const msg_satellite_t*) m.data; - wp->hdop = le_readu16(p->hdop); - wp->hdop /= 100; - wp->vdop = le_readu16(p->vdop); - wp->vdop /= 100; - wp->pdop = le_readu16(p->pdop); - wp->pdop /= 100; - } - message_free(&m); - return wp; -} - -//----------------------------------------------------------------------------- - -static void -delbin_list_units() -{ - int i; - for (i = 0; i < n_delbin_units; i++) { - printf("%u %s %s\n", - delbin_unit_info[i].unit_number, - delbin_unit_info[i].unit_serial_number, - delbin_unit_info[i].unit_name); - } -} - -static void -delbin_rw_init(const QString& fname) -{ - message_t m; - char buf[256]; - - if (!mkshort_handle) { - mkshort_handle = mkshort_new_handle(); - } - // Contrary to doc, it looks like there's a limit of 32 bytes - // and a null terminator is required, at least in F/W 2.6.210726 - // on a PN-40. - setshort_length(mkshort_handle, 31); - setshort_whitespace_ok(mkshort_handle, 1); - setshort_badchars(mkshort_handle, ""); - setshort_mustuniq(mkshort_handle, 1); - - delbin_os_ops.init(fname); - - // Often the first packet is part of an old message, sometimes it can - // confuse the first message read if we don't get rid of it - packet_read(buf); - // Send a break to clear any state from a previous failure - message_init_size(&m, MSG_BREAK_SIZE); - memset(m.data, 0, m.size); - message_write(MSG_BREAK, &m); - // get version info - m.size = 0; - message_write(MSG_VERSION, &m); - if (message_read(MSG_VERSION, &m)) { - const msg_version_t* p = (const msg_version_t*) m.data; - if (global_opts.debug_level >= DBGLVL_L) { - warning(MYNAME ": device %s %s\n", p->product, p->firmware); - } - if (opt_long_notes) { - use_extended_notes = TRUE; - } else if (strstr(p->product, "PN-20")) { - use_extended_notes = p->firmware[0] > '1' || - (p->firmware[0] == '1' && p->firmware[2] >= '6'); - } else if (strstr(p->product, "PN-30") || strstr(p->product, "PN-40")) { - use_extended_notes = p->firmware[0] > '2' || - (p->firmware[0] == '2' && p->firmware[2] >= '5'); - } else { - // assume PN-60 or later - use_extended_notes = TRUE; - } - delbin_unit_info[n_delbin_units].unit_number = n_delbin_units; - delbin_unit_info[n_delbin_units].unit_serial_number = xstrndup(p->serial, sizeof(p->serial)); - delbin_unit_info[n_delbin_units].unit_name = xstrndup(p->product, sizeof(p->product)); - n_delbin_units++; - } - message_free(&m); - - if (fname.length() > 4) { - if (fname.mid(4,4) == "list") { - delbin_list_units(); - exit(1); - } - } -} - -static void -delbin_rw_deinit(void) -{ - if (mkshort_handle) { - mkshort_del_handle(&mkshort_handle); - } - delbin_os_ops.deinit(); -} - -static void -delbin_read(void) -{ - if (doing_wpts) { - if (opt_getposn) { - waypt_add(read_position()); - } else { - read_waypoints(); - } - } - if (doing_trks) { - read_tracks(); - } - if (doing_rtes) { - read_routes(); - } -} - -static void -delbin_write(void) -{ - if (doing_wpts) { - message_t m; - device_max_waypoint = 1000; - message_init_size(&m, 0); - message_write(MSG_CAPABILITIES, &m); - if (message_read(MSG_CAPABILITIES, &m)) { - const msg_capabilities_t* p = (const msg_capabilities_t*) m.data; - device_max_waypoint = le_readu32(p->max_waypoints); - } - message_free(&m); - - if (opt_nuke_wpt) { - add_nuke(nuke_type_wpt); - } - write_waypoints(); - } - if (doing_trks) { - if (opt_nuke_trk) { - add_nuke(nuke_type_trk); - } - write_tracks(); - } - if (doing_rtes) { - if (opt_nuke_rte) { - add_nuke(nuke_type_rte); - } - write_routes(); - } -} - -static Waypoint* -delbin_rd_position(posn_status* status) -{ - return read_position(); -} - -ff_vecs_t delbin_vecs = { - ff_type_serial, - FF_CAP_RW_ALL, - delbin_rw_init, - delbin_rw_init, - delbin_rw_deinit, - delbin_rw_deinit, - delbin_read, - delbin_write, - NULL, - delbin_args, - CET_CHARSET_LATIN1, 1, - { delbin_rw_init, delbin_rd_position, delbin_rw_deinit } -}; - -//============================================================================= -// OS device I/O implementations - -#define VENDOR_ID 0x1163 -#define PRODUCT_ID 0x2020 - -//----------------------------------------------------------------------------- -// Windows -#ifdef HAVE_WDK - -#undef HAVE_LIBUSB - -#define WIN32_LEAN_AND_MEAN -#include -#include -// If hidsdi.h is not found, you need to download the Windows Driver Kit, -// from http://www.microsoft.com/whdc/Devtools/wdk/default.mspx -// You need to install 'build environments' and 'tools' from the SDK and -// follow the instructions in the Install.html to get MSVC to find the right -// headers and libraries. -#include -#include - -static HANDLE hid_handle; - -static void -win_os_init(const QString& fname) -{ - GUID hid_guid; - HDEVINFO dev_info; - SP_DEVICE_INTERFACE_DATA dev_int_data; - PHIDP_PREPARSED_DATA hid_ppd; - HIDP_CAPS hid_caps; - const char* busy = ""; - unsigned i; - - hid_handle = INVALID_HANDLE_VALUE; - HidD_GetHidGuid(&hid_guid); - dev_info = SetupDiGetClassDevs(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); - if (dev_info == INVALID_HANDLE_VALUE) { - fatal(MYNAME ": SetupDiGetClassDevs failed %u\n", GetLastError()); - } - dev_int_data.cbSize = sizeof(dev_int_data); - for (i = 0; SetupDiEnumDeviceInterfaces(dev_info, NULL, &hid_guid, i, &dev_int_data); i++) { - union { - SP_DEVICE_INTERFACE_DETAIL_DATA detail_data; - char buf[300]; - } u; - u.detail_data.cbSize = sizeof(u.detail_data); - if (SetupDiGetDeviceInterfaceDetail(dev_info, - &dev_int_data, &u.detail_data, sizeof(u.buf), NULL, NULL)) { - HANDLE h = CreateFile(u.detail_data.DevicePath, - FILE_READ_DATA | FILE_WRITE_DATA, 0, NULL, OPEN_EXISTING, 0, NULL); - if (h != INVALID_HANDLE_VALUE) { - HIDD_ATTRIBUTES hid_attr; - hid_attr.Size = sizeof(hid_attr); - if (HidD_GetAttributes(h, &hid_attr) && - hid_attr.VendorID == VENDOR_ID && hid_attr.ProductID == PRODUCT_ID) { - hid_handle = h; - break; - } - CloseHandle(h); - } else if (GetLastError() == ERROR_SHARING_VIOLATION && - strstr(u.detail_data.DevicePath, "1163") && - strstr(u.detail_data.DevicePath, "2020")) { - busy = " (device busy?)"; - } - } - } - SetupDiDestroyDeviceInfoList(dev_info); - if (hid_handle == INVALID_HANDLE_VALUE) { - fatal(MYNAME ": no DeLorme PN found%s\n", busy); - } - if (!HidD_GetPreparsedData(hid_handle, &hid_ppd)) { - fatal(MYNAME ": HidD_GetPreparsedData failed %u\n", GetLastError()); - } - if (!HidP_GetCaps(hid_ppd, &hid_caps)) { - fatal(MYNAME ": HidP_GetCaps failed %u\n", GetLastError()); - } - // report length includes report id - delbin_os_packet_size = hid_caps.InputReportByteLength - 1; - HidD_FreePreparsedData(hid_ppd); -} - -static void -win_os_deinit(void) -{ - CloseHandle(hid_handle); -} - -static unsigned -win_os_packet_read(void* buf) -{ - DWORD n; - char buf1[257]; - // first byte is report id - if (ReadFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) { - unsigned err = GetLastError(); - fatal(MYNAME ": ReadFile failed %u\n", err); - } - if (n > 0) { - n--; - } - memcpy(buf, buf1 + 1, n); - return n; -} - -static unsigned -win_os_packet_write(const void* buf, unsigned size) -{ - DWORD n; - char buf1[257]; - // first byte is report id - buf1[0] = 0; - memcpy(buf1 + 1, buf, size); - if (WriteFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) { - unsigned err = GetLastError(); - fatal(MYNAME ": WriteFile of %u bytes failed with %u. Size: %u Wrote: %d\n", - delbin_os_packet_size + 1, err, size, (int) n); - } - if (n > size) { - n = size; - } - return n; -} - -delbin_os_ops_t delbin_os_ops = { - win_os_init, - win_os_deinit, - win_os_packet_read, - win_os_packet_write -}; - -#endif // HAVE_WDK - -//----------------------------------------------------------------------------- -// MacOS X -#if __APPLE__ - -#undef HAVE_LIBUSB - -#include -#include -#include -#include -#include - -// IOHIDDeviceInterface121::getReport() does not work, it hangs the process -// in some sort of unkillable state. So reading is done via a separate thread -// with a run loop and interrupt report callback. Yuck. - -static IOHIDDeviceInterface122** device; -static pthread_t thread; -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; -static char* report_buf; -static char* packet_array[32]; -static unsigned packet_array_head; -static unsigned packet_array_tail; -static CFRunLoopRef run_loop; - -static void* -thread_func(void* run_loop_source) -{ - run_loop = CFRunLoopGetCurrent(); -#if __cplusplus - CFRunLoopAddSource(run_loop, (__CFRunLoopSource*) run_loop_source, kCFRunLoopDefaultMode); -#else - CFRunLoopAddSource(run_loop, run_loop_source, kCFRunLoopDefaultMode); -#endif - CFRunLoopRun(); - return NULL; -} - -static void -interrupt_report_cb(void* target, IOReturn result, void* refcon, void* sender, UInt32 bufferSize) -{ - memcpy(packet_array[packet_array_head], report_buf, delbin_os_packet_size); - pthread_mutex_lock(&mutex); - if (packet_array_head == packet_array_tail) { - pthread_cond_signal(&cond); - } - packet_array_head++; - packet_array_head &= sizeofarray(packet_array) - 1; - if (packet_array_head == packet_array_tail && global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": packet_array overrun, packets lost\n"); - } - pthread_mutex_unlock(&mutex); -} - -static void -mac_os_init(const QString& fname) -{ - CFMutableDictionaryRef dict = IOServiceMatching(kIOHIDDeviceKey); - io_service_t service; - IOCFPlugInInterface** plugin; - CFNumberRef cf_num; - CFRunLoopSourceRef run_loop_source; - int i; - kern_return_t kr; - HRESULT hr; - IOReturn ir; - SInt32 unused; - - i = VENDOR_ID; - cf_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i); - CFDictionaryAddValue(dict, CFSTR(kIOHIDVendorIDKey), cf_num); - CFRelease(cf_num); - i = PRODUCT_ID; - cf_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i); - CFDictionaryAddValue(dict, CFSTR(kIOHIDProductIDKey), cf_num); - CFRelease(cf_num); - service = IOServiceGetMatchingService(kIOMasterPortDefault, dict); - if (service == 0) { - fatal(MYNAME ": no DeLorme PN found\n"); - } - kr = IOCreatePlugInInterfaceForService( - service, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &unused); - if (kr) { - fatal(MYNAME ": IOCreatePlugInInterfaceForService failed 0x%x\n", (int)kr); - } - IOObjectRelease(service); - hr = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), (void**)&device); - if (hr) { - fatal(MYNAME ": QueryInterface failed 0x%x\n", (int)hr); - } - (*plugin)->Release(plugin); - ir = (*device)->open(device, kIOHIDOptionsTypeSeizeDevice); - if (ir) - fatal(MYNAME ": device open failed 0x%x - %s\n", (int)ir, - mach_error_string(ir)); - ir = (*device)->createAsyncEventSource(device, &run_loop_source); - if (ir) { - fatal(MYNAME ": createAsyncEventSource failed 0x%x\n", (int)ir); - } - delbin_os_packet_size = 64; - report_buf = (char*)xmalloc(delbin_os_packet_size); - for (i = sizeofarray(packet_array); i--;) { - packet_array[i] = (char*)xmalloc(delbin_os_packet_size); - } - ir = (*device)->setInterruptReportHandlerCallback( - device, report_buf, delbin_os_packet_size, interrupt_report_cb, NULL, NULL); - if (ir) { - fatal(MYNAME ": setInterruptReportHandlerCallback failed 0x%x\n", (int)ir); - } - i = pthread_create(&thread, NULL, thread_func, run_loop_source); - if (i) { - fatal(MYNAME ": pthread_create failed %d\n", i); - } -} - -static void -mac_os_deinit(void) -{ - void* unused; - unsigned i; - CFRunLoopStop(run_loop); - pthread_join(thread, &unused); - (*device)->Release(device); - xfree(report_buf); - for (i = sizeofarray(packet_array); i--;) { - xfree(packet_array[i]); - } -} - -static unsigned -mac_os_packet_read(void* buf) -{ - pthread_mutex_lock(&mutex); - while (packet_array_head == packet_array_tail) { - pthread_cond_wait(&cond, &mutex); - } - memcpy(buf, packet_array[packet_array_tail++], delbin_os_packet_size); - packet_array_tail &= sizeofarray(packet_array) - 1; - pthread_mutex_unlock(&mutex); - return delbin_os_packet_size; -} - -static unsigned -mac_os_packet_write(const void* buf, unsigned size) -{ - IOReturn r = (*device)->setReport( - device, kIOHIDReportTypeOutput, 0, (void*)buf, size, 2000, NULL, NULL, NULL); - if (r) { - fatal("setReport failed 0x%x\n", (int)r); - } - return size; -} - -delbin_os_ops_t delbin_os_ops = { - mac_os_init, - mac_os_deinit, - mac_os_packet_read, - mac_os_packet_write -}; - -#endif // __APPLE__ - -//----------------------------------------------------------------------------- -// libusb -#if HAVE_LIBUSB - -#include - -static struct usb_device* usb_dev; -static usb_dev_handle* usb_handle; -static int endpoint_in; -static int endpoint_out; - -static void -libusb_os_init(const QString& fname) -{ - struct usb_bus* bus; - const struct usb_endpoint_descriptor* endpoint_desc; - - usb_init(); - usb_find_busses(); - usb_find_devices(); - for (bus = usb_busses; usb_dev == NULL && bus; bus = bus->next) { - struct usb_device* d; - for (d = bus->devices; d; d = d->next) { - if (d->descriptor.idVendor == VENDOR_ID && d->descriptor.idProduct == PRODUCT_ID) { - usb_dev = d; - break; - } - } - } - if (usb_dev == NULL) { - fatal(MYNAME ": no DeLorme PN found\n"); - } - usb_handle = usb_open(usb_dev); - if (usb_handle == NULL) { - fatal(MYNAME ": %s\n", usb_strerror()); - } - - // Device has 1 configuration, 1 interface, 2 interrupt endpoints - if (usb_claim_interface(usb_handle, 0) < 0) { -#if LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP - if (usb_detach_kernel_driver_np(usb_handle, 0) < 0) { - warning(MYNAME ": %s\n", usb_strerror()); - } - if (usb_claim_interface(usb_handle, 0) < 0) -#endif - { - const char* s = usb_strerror(); - usb_close(usb_handle); - fatal(MYNAME ": %s\n", s); - } - } - endpoint_desc = usb_dev->config[0].interface[0].altsetting[0].endpoint; - delbin_os_packet_size = endpoint_desc[0].wMaxPacketSize; - endpoint_in = endpoint_desc[0].bEndpointAddress; - endpoint_out = endpoint_desc[1].bEndpointAddress; - if ((endpoint_in & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_OUT) { - int t = endpoint_in; - endpoint_in = endpoint_out; - endpoint_out = t; - } -} - -static void -libusb_os_deinit(void) -{ - usb_release_interface(usb_handle, 0); - usb_close(usb_handle); -} - -static unsigned -libusb_os_packet_read(void* buf) -{ - int n = usb_interrupt_read(usb_handle, endpoint_in, (char*) buf, delbin_os_packet_size, 2000); - if (n < 0) { - fatal(MYNAME ": %s\n", usb_strerror()); - } - return n; -} - -static unsigned -libusb_os_packet_write(const void* buf, unsigned size) -{ - int n = usb_interrupt_write(usb_handle, endpoint_out, (char*)buf, size, 2000); - if (n < 0) { - fatal(MYNAME ": %s\n", usb_strerror()); - } - return n; -} - -#if HAVE_LINUX_HID -static const delbin_os_ops_t libusb_os_ops = -#else -delbin_os_ops_t delbin_os_ops = -#endif -{ - libusb_os_init, - libusb_os_deinit, - libusb_os_packet_read, - libusb_os_packet_write -}; - -#endif // HAVE_LIBUSB - -//----------------------------------------------------------------------------- -// Linux -#if HAVE_LINUX_HID - -#include -#include -#include -#include -#include -#include -#include -#include - -// Read from hidraw, write to hiddev. Reading from hiddev does not work, -// and neither does writing to hidraw. -static int fd_hidraw; -static int fd_hiddev; - -static int linuxhid_os_init_status; - -static void -linuxhid_os_init(const QString& fname) -{ - struct hidraw_devinfo info; - struct hiddev_field_info finfo; - DIR* dir = NULL; - struct dirent* d; - - fd_hidraw = fd_hiddev = -1; - if (fname.startsWith("hid:")) { - char* raw_name = xstrdup(qPrintable(fname.mid(4))); - char* dev_name = strchr(raw_name, ','); - if (dev_name == NULL) { - fatal(MYNAME ": missing hiddev path\n"); - } - *dev_name++ = 0; - fd_hidraw = open(raw_name, O_RDONLY); - if (fd_hidraw < 0) { - fatal(MYNAME ": %s: %s\n", raw_name, strerror(errno)); - } - fd_hiddev = open(dev_name, O_WRONLY); - if (fd_hiddev < 0) { - fatal(MYNAME ": %s: %s\n", dev_name, strerror(errno)); - } - xfree(raw_name); - } else { - dir = opendir("/dev"); - } - while (dir && (d = readdir(dir)) != NULL) { - if (strncmp(d->d_name, "hidraw", 6) == 0) { - int fd1, fd2; - char raw_name[32]; - char dev_name[32]; - sprintf(raw_name, "/dev/%s", d->d_name); - fd1 = open(raw_name, O_RDONLY); - if (fd1 < 0) { - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": %s: %s\n", raw_name, strerror(errno)); - } - continue; - } - sprintf(dev_name, "/dev/usb/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1); - fd2 = open(dev_name, O_WRONLY); - if (fd2 < 0 && errno == ENOENT) { - sprintf(dev_name, "/dev/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1); - fd2 = open(dev_name, O_WRONLY); - } - if (fd2 < 0) { - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": %s: %s\n", dev_name, strerror(errno)); - } - close(fd1); - continue; - } - if (ioctl(fd1, HIDIOCGRAWINFO, &info) == 0 && - info.vendor == VENDOR_ID && info.product == PRODUCT_ID) { - fd_hidraw = fd1; - fd_hiddev = fd2; - break; - } - close(fd1); - close(fd2); - } - } - if (dir) { - closedir(dir); - } - if (fd_hidraw < 0) { - if (linuxhid_os_init_status == 0) { - fatal(MYNAME ": no DeLorme PN found\n"); - } - return; - } - finfo.report_type = HID_REPORT_TYPE_INPUT; - finfo.report_id = 0; - finfo.field_index = 0; - if (ioctl(fd_hiddev, HIDIOCGFIELDINFO, &finfo) < 0) { - warning(MYNAME ": HIDIOCGFIELDINFO: %s\n", strerror(errno)); - if (linuxhid_os_init_status == 0) { - exit(1); - } - return; - } - delbin_os_packet_size = finfo.maxusage; - linuxhid_os_init_status = 0; -} - -static void -linuxhid_os_deinit(void) -{ - close(fd_hidraw); - close(fd_hiddev); -} - -static unsigned -linuxhid_os_packet_read(void* buf) -{ - int n = read(fd_hidraw, buf, delbin_os_packet_size); - if (n < 0) { - fatal(MYNAME ": %s\n", strerror(errno)); - } - return n; -} - -static unsigned -linuxhid_os_packet_write(const void* buf, unsigned size) -{ - struct hiddev_usage_ref_multi urefm; - struct hiddev_report_info rinfo; - const uint8_t* p = (const uint8_t*) buf; - unsigned i; - - for (i = 0; i < size; i++) { - urefm.values[i] = p[i]; - } - urefm.num_values = size; - memset(&urefm.uref, 0, sizeof(urefm.uref)); - urefm.uref.report_type = HID_REPORT_TYPE_OUTPUT; - if (ioctl(fd_hiddev, HIDIOCSUSAGES, &urefm)) { - fatal(MYNAME ": HIDIOCSUSAGES: %s\n", strerror(errno)); - } - memset(&rinfo, 0, sizeof(rinfo)); - rinfo.report_type = HID_REPORT_TYPE_OUTPUT; - if (ioctl(fd_hiddev, HIDIOCSREPORT, &rinfo)) { - fatal(MYNAME ": HIDIOCSREPORT: %s\n", strerror(errno)); - } - return size; -} - -static const delbin_os_ops_t linuxhid_os_ops = { - linuxhid_os_init, - linuxhid_os_deinit, - linuxhid_os_packet_read, - linuxhid_os_packet_write -}; - -static void -linux_os_init(const QString& fname) -{ - // tell linuxhid_os_init not to exit - linuxhid_os_init_status = 1; - linuxhid_os_init(fname); - if (linuxhid_os_init_status == 0) { - delbin_os_ops = linuxhid_os_ops; - } else { -#if HAVE_LIBUSB - if (global_opts.debug_level >= DBGLVL_M) { - warning(MYNAME ": HID init failed, falling back to libusb\n"); - } - delbin_os_ops = libusb_os_ops; - delbin_os_ops.init(fname); -#else - fatal(MYNAME ": no DeLorme PN found\n"); -#endif - } -} - -delbin_os_ops_t delbin_os_ops = { - linux_os_init, - NULL, - NULL, - NULL -}; - -#endif // HAVE_LINUX_HID - -//----------------------------------------------------------------------------- -// stubs -#if !(HAVE_WDK || __APPLE__ || HAVE_LIBUSB || HAVE_LINUX_HID) -static void -stub_os_init(const QString& fname) -{ - fatal(MYNAME ": OS not supported\n"); -} -static void -stub_os_deinit(void) -{ -} -static unsigned -stub_os_packet_read(void* buf) -{ - return 0; -} -static unsigned -stub_os_packet_write(const void* buf, unsigned size) -{ - return 0; -} -delbin_os_ops_t delbin_os_ops = { - stub_os_init, - stub_os_deinit, - stub_os_packet_read, - stub_os_packet_write -}; -#endif -// end OS device I/O implementations section -//============================================================================= - -static const int track_color_bgr[] = { - 0x0000ff, // red - 0x00ffff, // yellow - 0x008000, // green - 0xff0000, // blue - 0x808080, // gray - 0xffffff, // white - 0, // black - 0xffff00, // cyan - 0xff00ff, // magenta - 0x00a5ff, // orange - 0x82004b, // indigo - 0xeea5ee // violet -}; - -static int track_color(unsigned i) -{ - int bgr = -1; - if (i < sizeofarray(track_color_bgr)) { - bgr = track_color_bgr[i]; - } - return bgr; -} - -static unsigned track_color_index(int bgr) -{ - unsigned i = sizeofarray(track_color_bgr); - do { - i--; - } while (i != 0 && track_color_bgr[i] != bgr); - return i; -} - -static const char* const waypoint_symbol_name[] = { - // 0 - "Red Map Pin", - "Dark Red Map Pin", - "Yellow Map Pin", - "Dark Yellow Map Pin", - "Green Map Pin", - "Dark Green Map Pin", - "Turquoise Map Pin", - "Dark Turquoise Map Pin", - "Blue Map Pin", - "Dark Blue Map Pin", - // 10 - "Gray Map Pin", - "Dark Gray Map Pin", - "Red Flag", - "Dark Red Flag", - "Yellow Flag", - "Dark Yellow Flag", - "Green Flag", - "Dark Green Flag", - "Turquoise Flag", - "Dark Turquoise Flag", - // 20 - "Blue Flag", - "Dark Blue Flag", - "Gray Flag", - "Dark Gray Flag", - "Red Dot", - "Dark Red Dot", - "Yellow Dot", - "Dark Yellow Dot", - "Green Dot", - "Dark Green Dot", - // 30 - "Turquoise Dot", - "Dark Turquoise Dot", - "Blue Dot", - "Dark Blue Dot", - "Gray Dot", - "Dark Gray Dot", - "Small Red Dot", - "Small Dark Red Dot", - "Small Yellow Dot", - "Small Dark Yellow Dot", - // 40 - "Small Green Dot", - "Small Dark Green Dot", - "Small Turquoise Dot", - "Small Dark Turquoise Dot", - "Small Blue Dot", - "Small Dark Blue Dot", - "Small Gray Dot", - "Small Dark Gray Dot", - "Arrow Up", - "Arrow Down", - // 50 - "Arrow Left", - "Arrow Right", - "Arrow Up Left", - "Arrow Up Right", - "Arrow Down Left", - "Arrow Down Right", - "Green Star", - "Yellow Square", - "Red X", - "Turquoise Circle", - // 60 - "Purple Triangle", - "American Flag", - "Stop", - "Parking", - "First Aid", - "Dining", - "Railroad Crossing", - "Heliport", - "Restroom", - "Information", - // 70 - "Diver Down", - "Exit", - "Health Facility", - "Police", - "Post Office", - "Mining", - "Danger", - "Money", - "Exclamation", - "Car", - // 80 - "Jeep", - "Truck", - "Tow Truck", - "Motor Home", - "School Bus", - "Four-wheeler", - "Snowmobile", - "Sailboat", - "Powerboat", - "Boat Launch", - // 90 - "Anchor", - "Buoy", - "Shipwreck", - "Glider Area", - "Private Airport", - "Public Airport", - "Military Airport", - "Military Base", - "House", - "Church", - // 100 - "Building", - "School", - "Lighthouse", - "Bridge", - "Radio Tower", - "Dam", - "Tunnel", - "Toll Booth", - "Gas Station", - "Lodging", - // 110 - "Telephone", - "Traffic Light", - "Fire Hydrant", - "Cemetery", - "Picnic Table", - "Tent", - "Shelter", - "Camper", - "Fire", - "Shower", - // 120 - "Drinking Water", - "Binoculars", - "Camera", - "Geocache", - "Geocache Found", - "Fishing Pole", - "Ice Fishing Trap Set", - "Ice Fishing Trap Up", - "Moose", - "Deer", - // 130 - "Bear", - "Bird", - "Duck", - "Fish", - "Deer Tracks", - "Animal Tracks", - "Bird Tracks", - "Birch Tree", - "Evergreen Tree", - "Deciduous Tree", - // 140 - "Flower Garden", - "Mountain", - "Cave", - "Beach", - "Hiking", - "Swimming", - "Bicycling", - "Kayaking", - "Canoeing", - "Water Skiing", - // 150 - "Cross-country Skiing", - "Downhill Skiing", - "Ice Skating", - "Dogsledding", - "Shooting", - "Golf Course", - "Ballpark", - // 157-182 added in PN-40 2.5 firmware - "Cache Found", - "Didn't Find It", - "My Cache", - // 160 - "Traditional Cache", - "Multi-Cache", - "Unknown Cache", - "Letterbox Hybrid", - "Whereigo Cache", - "Event Cache", - "Mega-Event Cache", - "Cache In Trash Out Event", - "EarthCache", - "Virtual Cache", - // 170 - "Webcam Cache", - "Waymark", - "NGS Benchmark", - "Write Note", - "Needs Maintenance", - "Final Location", - "Parking Area", - "Question to Answer", - "Reference Point", - "Stages of a Multicache", - // 180 - "Trailhead", - "Temporarily Disable Listing", - "Enable Listing", - // 183-222 added in PN-40 2.7 firmware - "Crane Truck", - "Forest Fire", - "Oil Derrick", - "Wind Turbine", - "Letter A", - "Letter B", - "Letter C", - // 190 - "Letter D", - "Letter E", - "Letter F", - "Letter G", - "Letter H", - "Letter I", - "Letter J", - "Letter K", - "Letter L", - "Letter M", - // 200 - "Letter N", - "Letter O", - "Letter P", - "Letter Q", - "Letter R", - "Letter S", - "Letter T", - "Letter U", - "Letter V", - "Letter W", - // 210 - "Letter X", - "Letter Y", - "Letter Z", - "Numeral 0", - "Numeral 1", - "Numeral 2", - "Numeral 3", - "Numeral 4", - "Numeral 5", - "Numeral 6", - // 220 - "Numeral 7", - "Numeral 8", - "Numeral 9" -}; - -static const char* -waypoint_symbol(unsigned i) -{ - const char* p = NULL; - if (i < sizeofarray(waypoint_symbol_name)) { - p = waypoint_symbol_name[i]; - } - return p; -} - -static unsigned -waypoint_symbol_index(const char* name) -{ - static unsigned last_result; - static char last_name[32]; - unsigned i = last_result; - - if (strncmp(name, last_name, sizeof(last_name)) != 0) { - i = sizeofarray(waypoint_symbol_name); - do { - i--; - } while (i != 0 && case_ignore_strcmp(name, waypoint_symbol_name[i]) != 0); - strncpy(last_name, name, sizeof(last_name)); - last_result = i; - } - return i; -} - -// vi: ts=4 sw=4 noexpandtab diff --git a/deprecated/delbin.cc b/deprecated/delbin.cc new file mode 100644 index 000000000..56735fc2a --- /dev/null +++ b/deprecated/delbin.cc @@ -0,0 +1,3373 @@ +/* + DeLorme PN-20/40 USB "DeLBin" protocol + + Copyright (C) 2009 Paul Cornett, pc-gpsb at bullseye.com + Copyright (C) 2005-2014 Robert Lipe, robertlipe+source@gpsbabel.orgg + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA + + */ + +#include "defs.h" +#include "src/core/xmltag.h" +#include +#include /* for atoi, sprintf */ +#include // atoi + +#define MYNAME "delbin" +static short_handle mkshort_handle; + +/* +Device documentation: +"DeLorme Binary GPS Format", delbin_user_interface_format_176.pdf +obtained here: http://forum.delorme.com/viewtopic.php?t=13846 + +Notes: +Initial development was done with a PN-40, firmware 2.4.123299. The test +device was upgraded to firmware 2.5.165506 during development. + +The "data size" in the message header includes the 4 trailer bytes, so it +is really the size of the whole message minus the header. + +Messages do not always start at the beginning of a packet. Every once in a +while, the start of the next message directly follows the end of the previous +one, in the same packet. + +The time before an unacknowledged message will be retransmitted by the +device is on the order of 2 to 4 seconds. + +Retrieving all tracks at once (using code 0 in message 0xb031) does not +seem to work, it hangs after the first track, maybe waiting for some +undocumented response message. + +Character encoding is not documented, appears to be 8859-1. + +The undocumented messages 0xaa01, 0xb015, 0xb016 and the use of the +"reserved" byte in message 0xb012 were discovered by examining the data +transferred between the device and DeLorme Topo 8.0. They may have been +added in the PN-40 2.5 firmware. +*/ + +//----------------------------------------------------------------------------- +// interface to platform-specific device I/O +typedef struct { + void (*init)(const QString& name); + void (*deinit)(void); + unsigned(*packet_read)(void*); + unsigned(*packet_write)(const void*, unsigned); +} delbin_os_ops_t; + +// really static, only extern so it can be forward declared +extern delbin_os_ops_t delbin_os_ops; + +static unsigned delbin_os_packet_size; +//----------------------------------------------------------------------------- + +// number of times to attempt a transfer before giving up +#define ATTEMPT_MAX 2 +// seconds to wait for expected message (actual time will be somewhat +// indeterminate, but at least READ_TIMEOUT - 1) +#define READ_TIMEOUT 6 + +// debug output: low, medium, high, higher +#define DBGLVL_L 1 +#define DBGLVL_M 2 +#define DBGLVL_H 3 +#define DBGLVL_H2 4 + +// Multiple unit support. +#define DELBIN_MAX_UNITS 32 +static struct { + unsigned int unit_number; + const char* unit_serial_number; + const char* unit_name; +} delbin_unit_info[DELBIN_MAX_UNITS]; +static int n_delbin_units; + +#define UNKNOWN_ELEV -2000000 + +#define sizeofarray(x) (sizeof(x) / sizeof(x[0])) + +static char* opt_getposn = NULL; +static char* opt_logs = NULL; +static char* opt_long_notes = NULL; +static char* opt_nuke_wpt = NULL; +static char* opt_nuke_trk = NULL; +static char* opt_nuke_rte = NULL; +/* If true, Order hint to match Cache Register and Topo 7 */ +static char* opt_hint_at_end = NULL; +static char* opt_gcsym = NULL; + + +static arglist_t delbin_args[] = { + { + "get_posn", &opt_getposn, "Return current position as a waypoint", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX + }, + { + "logs", &opt_logs, "Include groundspeak logs when writing", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX + }, + { + "long_notes", &opt_long_notes, "Use long waypoint notes regardless of PN version", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX + }, + { + "nukewpt", &opt_nuke_wpt, "Delete all waypoints before sending", NULL, ARGTYPE_BOOL, + ARG_NOMINMAX + }, + { + "nuketrk", &opt_nuke_trk, "Delete all tracks before sending", NULL, ARGTYPE_BOOL, + ARG_NOMINMAX + }, + { + "nukerte", &opt_nuke_rte, "Delete all routes before sending", NULL, ARGTYPE_BOOL, + ARG_NOMINMAX + }, + {"hint_at_end", &opt_hint_at_end, "If true, geocache hint at end of text", NULL, ARGTYPE_BOOL, ARG_NOMINMAX }, + {"gcsym", &opt_gcsym, "If set to 0, prefer user-provided symbols over Groundspeaks ones for geocaches", "1", ARGTYPE_BOOL, ARG_NOMINMAX }, + ARG_TERMINATOR +}; + +// Whether device understands message 0xb016 +static int use_extended_notes; + +// Device capabilities +static unsigned device_max_waypoint; + +static const char* waypoint_symbol(unsigned index); +static unsigned waypoint_symbol_index(const char* name); +static int track_color(unsigned index); +static unsigned track_color_index(int bgr); + +static unsigned waypoint_i; +static unsigned waypoint_n; +static Waypoint** wp_array; + +//----------------------------------------------------------------------------- +// Message ids and sizes. Only the needed ones are here. +// Note that "in" and "out" ids are named as in the device documentation, +// so "in" means to the device, "out" means from. +#define MSG_ACK 0xaa00 +#define MSG_BREAK 0xaa02 +#define MSG_BREAK_SIZE 33 +#define MSG_CAPABILITIES 0xb001 +#define MSG_DELETE 0xb005 +#define MSG_DELETE_SIZE 67 +#define MSG_ERROR 0xa003 +#define MSG_NAVIGATION 0xa010 +#define MSG_REQUEST_ROUTES 0xb051 +#define MSG_REQUEST_ROUTES_SIZE 65 +#define MSG_REQUEST_TRACKS 0xb031 +#define MSG_REQUEST_TRACKS_SIZE 33 +#define MSG_REQUEST_WAYPOINTS 0xb012 +#define MSG_REQUEST_WAYPOINTS_SIZE 15 +#define MSG_ROUTE_COUNT 0xb050 +#define MSG_ROUTE_HEADER_IN 0xb055 +#define MSG_ROUTE_HEADER_OUT 0xb052 +#define MSG_ROUTE_POINT_IN 0xb056 +#define MSG_ROUTE_POINT_OUT 0xb053 +#define MSG_ROUTE_SHAPE_IN 0xb057 +#define MSG_ROUTE_SHAPE_OUT 0xb054 +#define MSG_SATELLITE_INFO 0xa020 +#define MSG_TRACK_COUNT 0xb030 +#define MSG_TRACK_HEADER_IN 0xb035 +#define MSG_TRACK_HEADER_OUT 0xb032 +#define MSG_TRACK_POINT_IN 0xb036 +#define MSG_TRACK_POINT_OUT 0xb033 +#define MSG_TRANSFER_COMPLETE 0xaa04 +#define MSG_VERSION 0xa001 +#define MSG_WAYPOINT_COUNT 0xb010 +#define MSG_WAYPOINT_IN 0xb014 +#define MSG_WAYPOINT_OUT 0xb013 +// Undocumented: +// This one looks like MSG_ACK, except it also has a string in it that says +// something like "device is busy". The expected MSG_ACK usually immediately +// follows it, so the point of this one is unclear. +#define MSG_NACK 0xaa01 +// Long waypoint notes +#define MSG_WAYPOINT_NOTE_IN 0xb016 +#define MSG_WAYPOINT_NOTE_OUT 0xb015 + +//----------------------------------------------------------------------------- +// Message structures + +// Input Delete Message +// Message ID: 0xB005 +typedef enum { + nuke_type_wpt = 0, + nuke_type_trk = 1, + nuke_type_rte = 2, + // int nuke_map = 3; +} nuke_type; + +typedef enum { + nuke_mode_all = 0, + nuke_mode_single = 1 +} nuke_mode; + +typedef enum { + nuke_dest_internal = 0, + nuke_dest_sd = 1 +} nuke_dest; + +typedef struct { + uint8_t type; + uint8_t mode; + uint8_t location; + char object_name[64]; +} msg_delete_t; + +// Output Waypoint Message +// Message ID: 0xB013 +// Input Waypoint Message +// Message ID: 0xB014 +typedef struct { + uint8_t total[4]; // U32 + uint8_t index[4]; // U32 + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t latitude[4]; // S32 rad * 100000000 + uint8_t longitude[4]; // S32 rad * 100000000 + uint8_t elevation[4]; // F32 meters + uint8_t color; + uint8_t symbol; + uint8_t name_size; + char name[1]; + // note_size[2] U16 + // note[note_size] +} msg_waypoint_t; + +// undocumented, seen with PN-40 2.5 firmware +// output waypoint note +// Message ID: 0xB015 +// input waypoint note +// Message ID: 0xB016 +typedef struct { + uint8_t index[2]; + uint8_t total[2]; + uint8_t name_size; + char name[1]; + // note_size[2] + // note[note_size] +} msg_waypoint_note_t; + +// Output Track Point Message +// Message ID: 0xB033 +// Input Track Point Message +// Message ID: 0xB036 +typedef struct { + uint8_t total[4]; // U32 + uint8_t index[4]; // U32 + uint8_t number; + struct { + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t latitude[4]; // S32 rad * 100000000 + uint8_t longitude[4]; // S32 rad * 100000000 + uint8_t elevation[4]; // F32 meters + uint8_t speed[2]; // U16 km/h * 10 + uint8_t heading[2]; // U16 deg * 100 + uint8_t status; + } point[1]; +} msg_track_point_t; + +// Output Track Header (Name) Message +// Message ID: 0xB032 +typedef struct { + uint8_t total_tracks[2]; // U16 + uint8_t number[2]; // U16 + char name[32]; + uint8_t total_points[4]; // U32 + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t color[2]; // U16 + uint8_t distance[4]; // U32 m + uint8_t duration[4]; // U32 sec + uint8_t comment_size[2]; // U16 + char comment[1]; +} msg_track_header_t; + +// Input Upload Track Header Message +// Message ID: 0xB035 +typedef struct { + char name[32]; + uint8_t total_points[4]; // U32 + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t color[2]; // U16 + uint8_t comment_size[2]; // U16 + char comment[1]; +} msg_track_header_in_t; + +// Output Route Shape Message +// Message ID: 0xB054 +typedef struct { + uint8_t total[4]; // U32 + uint8_t index[4]; // U32 + uint8_t number; + uint8_t reserved; + struct { + uint8_t latitude[4]; // S32 rad * 100000000 + uint8_t longitude[4]; // S32 rad * 100000000 + } point[1]; +} msg_route_shape_t; + +// Output Route Point Message +// Message ID: 0xB053 +// Input Route Itin Point Message +// Message ID: 0xB056 +typedef struct { + uint8_t total[4]; // U32 + uint8_t index[4]; // U32 + char name[32]; + uint8_t latitude[4]; // S32 rad * 100000000 + uint8_t longitude[4]; // S32 rad * 100000000 + uint8_t time_from_start[4]; // U32 sec + uint8_t distance_from_start[4]; // F32 km + uint8_t bearing_in[2]; // U16 deg * 100 + uint8_t bearing_out[2]; // U16 deg * 100 + uint8_t bearing_next[2]; // U16 deg * 100 + uint8_t itinerary_type; + uint8_t turn_type; + uint8_t road_class[2]; // U16 + uint8_t feature_code[4]; // U32 + uint8_t exit_label_size; + char exit_label[1]; + // comment_size U8 + // comment[comment_size] + // shape_pt_count U32 +} msg_route_point_t; + +// Output Route Header (Name) Message +// Message ID: 0xB052 +typedef struct { + uint8_t total[2]; // U16 + uint8_t index[2]; // U16 + char name[64]; + uint8_t type; + uint8_t total_route_point[4]; // U32 + uint8_t total_shape_point[4]; // U32 +} msg_route_header_t; + +// Input Upload Route Header Message +// Message ID: 0xB055 +typedef struct { + char name[64]; + uint8_t type; + uint8_t total_route_point[4]; // U32 + uint8_t total_shape_point[4]; // U32 +} msg_route_header_in_t; + +// Output Navigation Message +// Message ID: 0xA010 +typedef struct { + uint8_t gps_week[2]; // U16 + uint8_t time_of_week[8]; // D64 sec + uint8_t year[2]; // U16 + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t satellites; + uint8_t latitude[8]; // D64 deg + uint8_t longitude[8]; // D64 deg + uint8_t elevation[8]; // D64 meters + uint8_t geoid_offset[2]; // S16 meters * 10 + uint8_t speed[4]; // F32 km/h + uint8_t heading[2]; // U16 deg * 100 + uint8_t magnetic_variation[2]; // S16 deg * 100 + uint8_t fix_status; +} msg_navigation_t; + +// Output Satellite Info Message +// Message ID: 0xA020 +typedef struct { + uint8_t gps_week[2]; // U16 + uint8_t time_of_week[8]; // D64 sec + uint8_t hdop[2]; // U16 + uint8_t vdop[2]; // U16 + uint8_t pdop[2]; // U16 + uint8_t number; + struct { + uint8_t prn; + uint8_t azimuth[2]; // S16 deg? * 100 + uint8_t elevation[2]; // S16 deg? * 100 + uint8_t Cn0[2]; // U16 snr * 100 + uint8_t status; + } sat[1]; +} msg_satellite_t; + +// Output Version Message +// Message ID: 0xA001 +typedef struct { + uint8_t firmware_version[4]; + char company[32]; + char product[32]; + char firmware[32]; + char gps_firmware[48]; + char serial[16]; + char extra[16]; +} msg_version_t; + +// Output Device Capabilities Message +// Message ID: 0xB001 +typedef struct { + uint8_t max_waypoints[4]; // U32 + uint8_t max_tracks[2]; // U16 + uint8_t max_track_points[4]; // U32 + uint8_t max_routes[2]; // U16 + uint8_t max_route_points[4]; // U32 + uint8_t max_route_shape_points[4]; // U32 + uint8_t max_maps[2]; // U16 + uint8_t min_map_version[2]; // U16 + uint8_t max_map_version[2]; // U16 + uint8_t total_internal_file_memory[4]; // U32 + uint8_t avail_internal_file_memory[4]; // U32 + uint8_t total_external_file_memory[4]; // U32 + uint8_t avail_external_file_memory[4]; // U32 +} msg_capabilities_t; + +//----------------------------------------------------------------------------- + +#if __APPLE__ || __linux +#include +#endif + +static void +debug_out(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fputs(MYNAME ": ", stderr); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static void +debug_out_time(const char* s) +{ +#if __APPLE__ || __linux + struct timeval tv; + gettimeofday(&tv, NULL); + debug_out("%u.%03u %s", (unsigned)tv.tv_sec & 0xf, (unsigned)tv.tv_usec / 1000, s); +#else + debug_out("%u %s", (unsigned)time(NULL) & 0xf, s); +#endif +} + +//----------------------------------------------------------------------------- + +static uint16_t +checksum(const uint8_t* p, unsigned n) +{ + int x = 0; + unsigned i; + for (i = n / 2; i > 0; i--) { + x += *p++; + x += *p++ << 8; + } + if (n & 1) { + x += *p; + } + return (uint16_t)-x; +} + +//----------------------------------------------------------------------------- +// OS packet read/write wrappers + +static unsigned +packet_read(void* buf) +{ + unsigned n = delbin_os_ops.packet_read(buf); + if (n == 0) { + fatal(MYNAME ": read 0\n"); + } + if (global_opts.debug_level >= DBGLVL_H) { + unsigned j; + const uint8_t* p = (const uint8_t*) buf; + + debug_out_time("pcktrd"); + for (j = 0; j < n; j++) { + warning(" %02x", p[j]); + } + if (global_opts.debug_level >= DBGLVL_H2) { + warning(" "); + for (j = 0; j < n; j++) { + int c = p[j]; + warning("%c", isprint(c) ? c : '.'); + } + } + warning("\n"); + } + return n; +} + +static void +packet_write(const void* buf, unsigned size) +{ + unsigned n; + if (global_opts.debug_level >= DBGLVL_H) { + unsigned j; + const uint8_t* p = (const uint8_t*) buf; + + debug_out_time("pcktwr"); + for (j = 0; j < size; j++) { + warning(" %02x", p[j]); + } + if (global_opts.debug_level >= DBGLVL_H2) { + warning(" "); + for (j = 0; j < size; j++) { + int c = p[j]; + warning("%c", isprint(c) ? c : '.'); + } + } + warning("\n"); + } + n = delbin_os_ops.packet_write(buf, size); + if (n != size) { + fatal(MYNAME ": short write %u %u\n", size, n); + } +} + +//----------------------------------------------------------------------------- + +// dynamically sized buffer with space reserved for message header and trailer +typedef struct { + // message data size + unsigned size; + // buffer size + unsigned capacity; + uint8_t* buf; + // convenience pointer to message data area + void* data; +} message_t; + +static void +message_init(message_t* m) +{ + m->capacity = 100; + m->buf = (uint8_t*)xmalloc(m->capacity); + m->data = m->buf + 2 + 8; +} + +static void +message_init_size(message_t* m, unsigned size) +{ + m->size = size; + m->capacity = 2 + 8 + size + 4; + m->buf = (uint8_t*)xmalloc(m->capacity); + m->data = m->buf + 2 + 8; +} + +static void +message_free(message_t* m) +{ + xfree(m->buf); + m->buf = NULL; + m->data = NULL; +} + +static void +message_ensure_size(message_t* m, unsigned size) +{ + m->size = size; + if (m->capacity < 2 + 8 + size + 4) { + m->capacity = 2 + 8 + size + 4; + xfree(m->buf); + m->buf = (uint8_t*)xmalloc(m->capacity); + m->data = m->buf + 2 + 8; + } +} + +static unsigned +message_get_id(const message_t* m) +{ + return le_readu16(m->buf + 4); +} + +//----------------------------------------------------------------------------- + +static void +message_write(unsigned msg_id, message_t* m) +{ + unsigned chksum; + unsigned count; + unsigned n; + uint8_t* p = m->buf; + + // header (2 start bytes filled in later) + p[2] = 0xdb; + p[3] = 0xfe; + le_write16(p + 4, msg_id); + // "data size" includes 4 trailer bytes + le_write16(p + 6, m->size + 4); + chksum = checksum(p + 2, 6); + le_write16(p + 8, chksum); + // message data (filled in by caller) + chksum = checksum((uint8_t*) m->data, m->size); + n = 2 + 8 + m->size; + // trailer (checksum and marker bytes) + le_write16(p + n, chksum); + p[n + 2] = 0xad; + p[n + 3] = 0xbc; + // size of message not counting packet start bytes + count = 8 + m->size + 4; + do { + const uint8_t save0 = p[0]; + const uint8_t save1 = p[1]; + n = delbin_os_packet_size - 2; + if (n > count) { + n = count; + } + // doc. says 0x20, device sends 0, probably ignored + p[0] = 0x20; + // valid bytes in packet after first 2 + p[1] = n; + packet_write(p, 2 + n); + p[0] = save0; + p[1] = save1; + p += n; + count -= n; + } while (count != 0); + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": sent %x\n", msg_id); + } +} + +// read from the payload of a single packet +static unsigned +read_depacketize_1(uint8_t** p, unsigned n, int new_packet) +{ + static uint8_t buf[256]; + static unsigned buf_i, buf_n; + if (new_packet) { + buf_n = 0; + } + while (buf_n == 0) { + packet_read(buf); + if (buf[1] <= delbin_os_packet_size - 2) { + buf_n = buf[1]; + buf_i = 2; + } + } + *p = buf + buf_i; + if (n > buf_n) { + n = buf_n; + } + buf_n -= n; + buf_i += n; + return n; +} + +// read from packet payloads until request is fulfilled +static void +read_depacketize(uint8_t* buf, unsigned n) +{ + while (n) { + uint8_t* p; + unsigned nn = read_depacketize_1(&p, n, FALSE); + memcpy(buf, p, nn); + n -= nn; + buf += nn; + } +} + +// Get one valid message. +// If a corrupted message with the right id is seen, return failure (0). +static unsigned +message_read_1(unsigned msg_id, message_t* m) +{ + unsigned id; + for (;;) { + unsigned total; + unsigned n; + uint8_t buf[8]; + uint8_t* p; + + n = read_depacketize_1(&p, 8, FALSE); + memset(buf, 0, 8); + memcpy(buf, p, n); + while (buf[0] != 0xdb || buf[1] != 0xfe || checksum(buf, 6) != le_readu16(buf + 6)) { + // try for a message start at the beginning of next packet + n = read_depacketize_1(&p, 8, TRUE); + memset(buf, 0, 8); + memcpy(buf, p, n); + } + id = le_readu16(buf + 2); + total = le_readu16(buf + 4); + message_ensure_size(m, total - 4); + // copy in message head, really only need id field, do the rest for debugging + m->buf[0] = m->buf[1] = 0; + memcpy(m->buf + 2, buf, 8); + // read message body and trailer + read_depacketize((uint8_t*) m->data, total); + p = (uint8_t*)m->data + m->size; + if (checksum((uint8_t*) m->data, m->size) == le_readu16(p) && + p[2] == 0xad && p[3] == 0xbc) { + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": received %x\n", id); + } + break; + } + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": corrupted message %x\n", id); + } + if (id == msg_id) { + id = 0; + break; + } + } + return id; +} + +// Send MSG_ACK for given message +static void +message_ack(unsigned id, const message_t* m) +{ + message_t ack; + char* p1; + const char* p2 = (const char*) m->data; + switch (id) { + case MSG_ACK: + case MSG_NACK: + case MSG_NAVIGATION: + case MSG_SATELLITE_INFO: + // don't ack these + return; + } + message_init_size(&ack, 4); + p1 = (char*) ack.data; + // ack payload is id and body checksum of acked message + le_write16(p1, id); + p1[2] = p2[m->size]; + p1[3] = p2[m->size + 1]; + message_write(MSG_ACK, &ack); + message_free(&ack); +} + +// Get specific message, ignoring others. Sends ACK for non-interval messages. +// Gives up after at least READ_TIMEOUT-1 seconds have passed. +static int +message_read(unsigned msg_id, message_t* m) +{ + unsigned id; + time_t time_start = time(NULL); + + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": looking for %x\n", msg_id); + } + for (;;) { + id = message_read_1(msg_id, m); + if (id == 0) { + break; + } + if (id == MSG_ERROR) { + const uint8_t* p = (const uint8_t*) m->data; + fatal(MYNAME ": device error %u: \"%s\"\n", *p, p + 1); + } + message_ack(id, m); + if (id == msg_id || time(NULL) - time_start >= READ_TIMEOUT) { + break; + } + } + return id == msg_id; +} + +// Read a sequence of messages, up to a MSG_TRANSFER_COMPLETE +static int +get_batch(message_t** array, unsigned* n) +{ + int success = 1; + unsigned array_max = 100; + message_t* a = (message_t*) xmalloc(array_max * sizeof(message_t)); + unsigned i = 0; + unsigned id; + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": begin get_batch\n"); + } + do { + time_t time_start = time(NULL); + if (i == array_max) { + message_t* old_a = a; + array_max += array_max; + a = (message_t*) xmalloc(array_max * sizeof(message_t)); + memcpy(a, old_a, i * sizeof(message_t)); + xfree(old_a); + } + message_init(&a[i]); + for (;;) { + id = message_read_1(0, &a[i]); + switch (id) { + case MSG_NAVIGATION: + if (time(NULL) - time_start >= READ_TIMEOUT) { + success = 0; + break; + } + // fall through + case MSG_ACK: + case MSG_NACK: + case MSG_SATELLITE_INFO: + continue; + } + break; + } + message_ack(id, &a[i]); + i++; + } while (success && id != MSG_TRANSFER_COMPLETE); + if (success) { + *array = a; + *n = i - 1; + message_free(&a[*n]); + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": end get_batch, %u messages\n", *n); + } + } else { + while (i--) { + message_free(&a[i]); + } + xfree(a); + *array = NULL; + *n = 0; + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": end get_batch, failed\n"); + } + } + return success; +} + +typedef struct { + unsigned msg_id; + message_t msg; +} batch_array_t; + +static batch_array_t* batch_array; + +static unsigned batch_array_max; +static unsigned batch_array_i; + +// add a message to sequence that will later be sent all at once +static void +add_to_batch(unsigned id, message_t* m) +{ + if (batch_array_i == batch_array_max) { + char* old = (char*) batch_array; + if (batch_array_max == 0) { + batch_array_max = 50; + } + batch_array_max += batch_array_max; + batch_array = (batch_array_t*) xmalloc(batch_array_max * sizeof(*batch_array)); + if (batch_array_i) { + memcpy(batch_array, old, batch_array_i * sizeof(*batch_array)); + xfree(old); + } + } + batch_array[batch_array_i].msg_id = id; + batch_array[batch_array_i].msg = *m; + batch_array_i++; + memset(m, 0, sizeof(*m)); +} + +// send an accumulated sequence of messages +static void +send_batch(void) +{ + message_t m; + const unsigned n = batch_array_i; + unsigned i; + unsigned progress = 0; + + message_init(&m); + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": begin send_batch, %u messages\n", n); + } + for (i = 0; i < n; i++) { + unsigned timeout_count = 0; + time_t time_start = time(NULL); + + // Can't really trigger this off either i or n as we don't + // know how the various packets map to actual waypts. + if (global_opts.verbose_status && + (batch_array[i].msg_id == MSG_WAYPOINT_IN)) { + waypt_status_disp(waypoint_n, ++progress); + } + + message_write(batch_array[i].msg_id, &batch_array[i].msg); + for (;;) { + unsigned id = message_read_1(0, &m); + switch (id) { + case MSG_ACK: + break; + case MSG_NAVIGATION: + if (time(NULL) - time_start >= 2) { + if (timeout_count) { + fatal(MYNAME ": send_batch timed out\n"); + } + timeout_count++; + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": re-sending %x\n", batch_array[i].msg_id); + } + message_write(batch_array[i].msg_id, &batch_array[i].msg); + time_start = time(NULL); + } + // fall through + case MSG_NACK: + case MSG_SATELLITE_INFO: + continue; + default: + warning(MYNAME ": unexpected response message %x during send_batch\n", id); + continue; + } + break; + } + } + message_read(MSG_TRANSFER_COMPLETE, &m); + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": end send_batch\n"); + } + for (i = n; i--;) { + message_free(&batch_array[i].msg); + } + xfree(batch_array); + message_free(&m); + batch_array_i = batch_array_max = 0; +} + +//----------------------------------------------------------------------------- +// Coordinate conversion + +static double +delbin_rad2deg(int32_t x) +{ + return x * ((180 / M_PI) / 100000000); +} + +static int32_t +delbin_deg2rad(double x) +{ + return (int32_t)(x * ((M_PI / 180) * 100000000)); +} + +//----------------------------------------------------------------------------- +// Waypoint reading + +static time_t +decode_time(const uint8_t* p) +{ + struct tm t; + t.tm_year = p[0]; + t.tm_mon = p[1] - 1; + t.tm_mday = p[2]; + t.tm_hour = p[3]; + t.tm_min = p[4]; + t.tm_sec = p[5]; + return mkgmtime(&t); +} + +static Waypoint* +decode_waypoint(const void* data) +{ + Waypoint* wp = new Waypoint; + const msg_waypoint_t* p = (const msg_waypoint_t*)data; + const char* s; + float f; + + wp->SetCreationTime(decode_time(&p->year)); + wp->latitude = delbin_rad2deg(le_read32(p->latitude)); + wp->longitude = delbin_rad2deg(le_read32(p->longitude)); + f = le_read_float(p->elevation); + if (f > UNKNOWN_ELEV) { + wp->altitude = f; + } + wp->icon_descr = waypoint_symbol(p->symbol); +// if (!wp->icon_descr.isNull()) { +// wp->icon_descr = wp->icon_descr; +// } + if (p->name_size && p->name[0]) { + wp->description = p->name; + } + s = p->name + p->name_size; + if (le_readu16(s) && s[2]) { + wp->notes = xstrdup(s + 2); + } + return wp; +} + +static void +read_waypoints(void) +{ + message_t m; + message_t* msg_array; + unsigned msg_array_n; + Waypoint* wp = NULL; + unsigned n_point; + unsigned notes_i = 0; + unsigned notes_max = 0; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + // get number of waypoints + for (;;) { + m.size = 0; + message_write(MSG_WAYPOINT_COUNT, &m); + if (message_read(MSG_WAYPOINT_COUNT, &m)) { + break; + } + if (--attempt == 0) { + fatal(MYNAME ": reading waypoint count failed\n"); + } + } + n_point = le_readu32(m.data); + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": %u waypoints\n", n_point); + } + if (n_point == 0) { + message_free(&m); + return; + } + // get waypoint messages + attempt = ATTEMPT_MAX; + for (;;) { + m.size = MSG_REQUEST_WAYPOINTS_SIZE; + memset(m.data, 0, m.size); + // This byte is documented as reserved. Setting it to 3 is required to get + // extended notes (message 0xb015) with PN-40 firmware 2.5. + // Whether it has any effect with earlier firmware or the PN-20 is unknown. + ((char*)m.data)[1] = 3; + message_write(MSG_REQUEST_WAYPOINTS, &m); + if (get_batch(&msg_array, &msg_array_n)) { + break; + } + if (--attempt == 0) { + fatal(MYNAME ": reading waypoints failed\n"); + } + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": timed out reading waypoints, retrying\n"); + } + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + // process waypoint messages + for (i = 0; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_WAYPOINT_OUT) { + wp = decode_waypoint(msg_array[i].data); + waypt_add(wp); + notes_i = 0; + notes_max = 0; + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": read waypoint '%s'\n", qPrintable(wp->description)); + } + } else if (wp && id == MSG_WAYPOINT_NOTE_OUT) { + const msg_waypoint_note_t* p = (const msg_waypoint_note_t*) msg_array[i].data; + const char* s = p->name + p->name_size; + unsigned nn = le_readu16(s); + if (notes_max < notes_i + nn) { +#if NEW_STRINGS +// This section needs a serious rethinking. +#else + char* old = wp->notes; +#endif + if (notes_max == 0) { + notes_max = nn; + } + do { + notes_max += notes_max; + } while (notes_max < notes_i + nn); + wp->notes = (char*) xmalloc(notes_max); +#if NEW_STRINGS +#else + if (old) { + memcpy(wp->notes, old, notes_i); + xfree(old); + } +#endif + } + if (nn) { +#if NEW_STRINGS + // Is this really what this code was trying to do? + wp->notes += QString::fromUtf8(s + 2, nn); +#else + memcpy(wp->notes + notes_i, s + 2, nn); +#endif + notes_i += nn; + if (wp->notes[notes_i - 1] == 0) { + notes_i--; + } + } + } else { + fatal(MYNAME ": unexpected message %x while reading waypoints\n", id); + } + message_free(&msg_array[i]); + } + xfree(msg_array); +} + +//----------------------------------------------------------------------------- +// Waypoint writing + +static void +encode_time(time_t time_, uint8_t* p) +{ + const struct tm* t = gmtime(&time_); + p[0] = t->tm_year; + p[1] = t->tm_mon + 1; + p[2] = t->tm_mday; + p[3] = t->tm_hour; + p[4] = t->tm_min; + p[5] = t->tm_sec; +} + +static void +get_gc_notes(const Waypoint* wp, int* symbol, char** notes, unsigned* notes_size) +{ + fs_xml* fs_gpx; + xml_tag* root = NULL; + gbfile* fd = gbfopen(NULL, "w", MYNAME); + const char* size = NULL; + int gc_sym = 0; + + switch (wp->gc_data->type) { + case gt_traditional: + gc_sym = 160; + break; + case gt_multi: + gc_sym = 161; + break; + case gt_virtual: + gc_sym = 169; + break; + case gt_letterbox: + gc_sym = 163; + break; + case gt_event: + gc_sym = 165; + break; + case gt_suprise: + gc_sym = 162; + break; + case gt_webcam: + gc_sym = 170; + break; + case gt_earth: + gc_sym = 168; + break; + case gt_benchmark: + gc_sym = 172; + break; + case gt_cito: + gc_sym = 167; + break; + case gt_mega: + gc_sym = 166; + break; + case gt_wherigo: + gc_sym = 164; + break; + case gt_unknown: + case gt_locationless: + case gt_ape: + break; + } + if (0 == (wp->icon_descr.compare("Geocache Found"))) { + gc_sym = 124; + } + if (!wp->description.isEmpty()) { + gbfputs(wp->description, fd); + if (!wp->gc_data->placer.isEmpty()) { + gbfprintf(fd, " by %s", CSTR(wp->gc_data->placer)); + } + gbfputc('\n', fd); + } + + gbfprintf(fd, "Cache ID: %s\n", CSTRc(wp->shortname)); + if (gc_sym && opt_gcsym && atoi(opt_gcsym)) { + gbfprintf(fd, "%s\n", waypoint_symbol(gc_sym)); + *symbol = gc_sym; + } else if (!wp->icon_descr.isNull()) { + gbfprintf(fd, "%s\n", CSTR(wp->icon_descr)); + } + switch (wp->gc_data->container) { + case gc_micro: + size = "Micro"; + break; + case gc_small: + size = "Small"; + break; + case gc_regular: + size = "Regular"; + break; + case gc_large: + size = "Large"; + break; + case gc_unknown: + size = "Not Chosen" ; + break; + case gc_other: + size = "Other"; + break; + // Device has no symbol for this, but this is what Topo sends. + case gc_virtual: + size = "Virtual"; + break; + default: + break; + } + if (size) { + gbfprintf(fd, "SIZE: %s\n", size); + } + if (wp->gc_data->diff % 10) { + gbfprintf(fd, "D%.1f", wp->gc_data->diff / 10.0); + } else { + gbfprintf(fd, "D%u", wp->gc_data->diff / 10); + } + if (wp->gc_data->terr % 10) { + gbfprintf(fd, "/T%.1f\n", wp->gc_data->terr / 10.0); + } else { + gbfprintf(fd, "/T%u\n", wp->gc_data->terr / 10); + } + if (!wp->gc_data->hint.isEmpty() && !opt_hint_at_end) { + gbfprintf(fd, "HINT: %s\n", CSTR(wp->gc_data->hint)); + } + if (!wp->gc_data->desc_short.utfstring.isEmpty() || !wp->gc_data->desc_long.utfstring.isEmpty()) { + gbfputs("DESC: ", fd); + if (!wp->gc_data->desc_short.utfstring.isEmpty()) { + char* s1 = strip_html(&wp->gc_data->desc_short); + char* s2 = cet_str_utf8_to_any(s1, global_opts.charset); + gbfprintf(fd, "%s\n", s2); + xfree(s2); + xfree(s1); + } + if (!wp->gc_data->desc_long.utfstring.isEmpty()) { + char* s1 = strip_html(&wp->gc_data->desc_long); + char* s2 = cet_str_utf8_to_any(s1, global_opts.charset); + gbfputs(s2, fd); + xfree(s2); + xfree(s1); + } + } + fs_gpx = (fs_xml*)fs_chain_find(wp->fs, FS_GPX); + if (opt_logs && fs_gpx && fs_gpx->tag) { + root = xml_findfirst(fs_gpx->tag, "groundspeak:logs"); + } + if (root) { + xml_tag* curlog = xml_findfirst(root, "groundspeak:log"); + if (curlog) { + gbfputs("\nLOG:\n", fd); + } + for (; curlog; curlog = xml_findnext(root, curlog, "groundspeak:log")) { + xml_tag* logpart = xml_findfirst(curlog, "groundspeak:type"); + if (logpart) { + gbfprintf(fd, "%s\n", CSTR(logpart->cdata)); + } + logpart = xml_findfirst(curlog, "groundspeak:date"); + if (logpart) { + time_t logtime = xml_parse_time(logpart->cdata).toTime_t(); + const struct tm* logtm = gmtime(&logtime); + gbfprintf(fd, "%d-%02d-%02d ", logtm->tm_year + 1900, logtm->tm_mon + 1, logtm->tm_mday); + } + logpart = xml_findfirst(curlog, "groundspeak:finder"); + if (logpart) { + char* s = cet_str_utf8_to_any(CSTR(logpart->cdata), global_opts.charset); + gbfputs(s, fd); + xfree(s); + } + logpart = xml_findfirst(curlog, "groundspeak:text"); + if (logpart) { + char* s = cet_str_utf8_to_any(CSTR(logpart->cdata), global_opts.charset); + gbfprintf(fd, ", %s", s); + xfree(s); + } + gbfputc('\n', fd); + } + } + if (!wp->gc_data->hint.isEmpty() && opt_hint_at_end) { + gbfprintf(fd, "\nHINT: %s\n", CSTR(wp->gc_data->hint)); + } + gbfputc(0, fd); + *notes_size = fd->memlen; + *notes = (char*) xmalloc(*notes_size); + memcpy(*notes, fd->handle.mem, *notes_size); + gbfclose(fd); +} + +static void +write_waypoint_notes(const char* notes, unsigned size, const char* name) +{ + message_t m; + const unsigned name_size = strlen(name) + 1; + const unsigned bytes_per_msg = (10 * (delbin_os_packet_size - 2)) - name_size - 20; + const unsigned msg_count = (size + (bytes_per_msg - 1)) / bytes_per_msg; + unsigned i = 1; + + do { + char* pp; + unsigned n = bytes_per_msg; + msg_waypoint_note_t* p; + message_init_size(&m, 2 + 2 + 1 + name_size + 2 + bytes_per_msg); + p = (msg_waypoint_note_t*) m.data; + le_write16(p->index, i++); + le_write16(p->total, msg_count); + p->name_size = name_size; + memcpy(p->name, name, p->name_size); + pp = p->name + p->name_size; + if (n > size) { + n = size; + } + le_write16(pp, n); + pp += 2; + memcpy(pp, notes, n); + pp += n; + if (*(pp - 1)) { + *pp++ = 0; + } + notes += n; + size -= n; + m.size = pp - (char*)p; + add_to_batch(MSG_WAYPOINT_NOTE_IN, &m); + } while (size != 0); +} + +static void +add_nuke(nuke_type type) +{ + message_t m; + msg_delete_t* p; + + message_init_size(&m, MSG_DELETE_SIZE); + p = (msg_delete_t*) m.data; + p->type = type; + p->mode = nuke_mode_all; + p->location = nuke_dest_internal; + memset(p->object_name, 0, sizeof(p->object_name)); + + // MSG_DELETE generates a MSG_TRANSFER_COMPLETE, + // so use the batch facility to wait for it + add_to_batch(MSG_DELETE, &m); + send_batch(); +} + +static void +write_waypoint(const Waypoint* wp) +{ + message_t m; + msg_waypoint_t* p; + QString name = wp->shortname; + char* notes; + unsigned name_size; + unsigned notes_size = 0; + unsigned extended_notes_size = 0; + const char* notes_freeable = NULL; + int symbol = -1; + float elev = UNKNOWN_ELEV; + char* pp; + + if (wp->EmptyGCData()) { + notes = xstrdup(wp->notes); + if (notes == NULL && wp->description.isEmpty() && wp->shortname != wp->description) { + notes = xstrdup(wp->description); + } + if (notes) { + notes_size = strlen(notes) + 1; + } + } else { + get_gc_notes(wp, &symbol, ¬es, ¬es_size); + notes_freeable = notes; + if (!wp->description.isEmpty()) { + name = mkshort(mkshort_handle, wp->description); + } + } + + if (notes_size > 800) { + if (use_extended_notes) { + extended_notes_size = notes_size; + notes_size = 1; + } else { + notes_size = 800; + } + } + + name_size = strlen(CSTRc(name)) + 1; + if (name_size > 255) { + name_size = 255; + } + message_init_size(&m, 31 + name_size + notes_size); + p = (msg_waypoint_t*) m.data; + + waypoint_i++; + le_write32(p->total, waypoint_n); + le_write32(p->index, waypoint_i); + encode_time(wp->GetCreationTime().toTime_t(), &p->year); + le_write32(p->latitude, delbin_deg2rad(wp->latitude)); + le_write32(p->longitude, delbin_deg2rad(wp->longitude)); + if (wp->altitude > unknown_alt) { + elev = wp->altitude; + } + le_write_float(p->elevation, elev); + if (symbol < 0) { + symbol = 0; + if (!wp->icon_descr.isNull()) { + symbol = waypoint_symbol_index(CSTR(wp->icon_descr)); + } + } + p->symbol = symbol; + p->name_size = name_size; + memcpy(p->name, CSTRc(name), name_size - 1); + p->name[name_size - 1] = 0; + pp = p->name + name_size; + m.size = (pp + 2 + notes_size) - (char*)p; + if (extended_notes_size) { + le_write16(pp, 0xffff); + pp[2] = 0; + } else { + le_write16(pp, notes_size); +#if NEW_STRINGS +#else + if (notes) { + memcpy(pp + 2, notes, notes_size - 1); + pp[2 + notes_size - 1] = 0; + } +#endif + } + + add_to_batch(MSG_WAYPOINT_IN, &m); + + if (extended_notes_size) { + write_waypoint_notes(notes, extended_notes_size, CSTRc(name)); + } + if (notes_freeable) { + xfree(notes_freeable); + } + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": wrote waypoint %u '%s'\n", waypoint_i, qPrintable(name)); + } +} + +static void +write_waypoints(void) +{ + message_t m; + unsigned device_n = 0; + + waypoint_i = 0; + waypoint_n = waypt_count(); + if (waypoint_n > device_max_waypoint) { + fatal(MYNAME ": waypoint count (%u) exceeds device limit (%u)\n", + waypoint_n, device_max_waypoint); + } + + message_init_size(&m, 0); + message_write(MSG_WAYPOINT_COUNT, &m); + if (message_read(MSG_WAYPOINT_COUNT, &m)) { + device_n = le_readu32(m.data); + } + + waypt_disp_all(write_waypoint); + send_batch(); + + if (device_n + waypoint_n > device_max_waypoint) { + m.size = 0; + message_write(MSG_WAYPOINT_COUNT, &m); + if (message_read(MSG_WAYPOINT_COUNT, &m) && + le_readu32(m.data) == device_max_waypoint) { + warning(MYNAME ": waypoint count (%u already on device + %u added = %u)" + " exceeds device limit (%u), some may have been discarded\n", + device_n, waypoint_n, device_n + waypoint_n, device_max_waypoint); + } + } + message_free(&m); +} + +//----------------------------------------------------------------------------- +// Track reading + +static void +decode_sat_fix(Waypoint* wp, const uint8_t status) +{ + switch (status & 3) { + case 1: + wp->fix = fix_none; + break; + case 2: + wp->fix = fix_2d; + break; + case 3: + wp->fix = fix_3d; + if (status & 4) { + wp->fix = fix_dgps; + } + break; + } +} + +static void +decode_track_point(const void* data, unsigned* wp_array_i, unsigned max_point) +{ + const msg_track_point_t* p = (const msg_track_point_t*) data; + const unsigned n = p->number; + unsigned i; + unsigned j = *wp_array_i; + + if (j + n > max_point) { + fatal(MYNAME ": read too many track points\n"); + } + for (i = 0; i < n; i++, j++) { + Waypoint* wp = new Waypoint; + float elev = le_read_float(p->point[i].elevation); + wp_array[j] = wp; + wp->SetCreationTime(decode_time(&p->point[i].year)); + wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude)); + wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude)); + if (elev > UNKNOWN_ELEV) { + wp->altitude = elev; + } + wp->speed = le_readu16(p->point[i].speed); + wp->speed *= (100.0f / (60 * 60)); + wp->wpt_flags.speed = 1; + decode_sat_fix(wp, p->point[i].status); + wp->wpt_flags.new_trkseg = (p->point[i].status & 0x10) != 0; + } + *wp_array_i = j; +} + +static void +read_track(route_head* track) +{ + message_t m; + message_t* msg_array; + const msg_track_header_t* p; + unsigned msg_array_n; + unsigned wp_array_i = 0; + unsigned n_point; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + // read track messages + for (;;) { + m.size = MSG_REQUEST_TRACKS_SIZE; + memset(m.data, 0, m.size); + ((char*)m.data)[0] = 1; // Download single track + strcpy((char*)m.data + 1, CSTRc(track->rte_name)); + message_write(MSG_REQUEST_TRACKS, &m); + if (get_batch(&msg_array, &msg_array_n)) { + break; + } + if (--attempt == 0) { + fatal(MYNAME ": reading track '%s' failed\n", qPrintable(track->rte_name)); + } + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": timed out reading track '%s', retrying\n", qPrintable(track->rte_name)); + } + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_TRACK_HEADER_OUT) { + fatal(MYNAME ": reading track '%s' failed (missing track header)\n", qPrintable(track->rte_name)); + } + // process track messages + p = (const msg_track_header_t*) msg_array[0].data; + if (le_readu16(p->comment_size)) { + track->rte_desc = p->comment; + } + track->line_color.bbggrr = track_color(p->color[0]); + n_point = le_readu32(p->total_points); + wp_array = (Waypoint**) xcalloc(n_point, sizeof(*wp_array)); + message_free(&msg_array[0]); + for (i = 1; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_TRACK_POINT_OUT) { + decode_track_point(msg_array[i].data, &wp_array_i, n_point); + } else { + fatal(MYNAME ": unexpected message %x while reading track '%s'\n", id, qPrintable(track->rte_name)); + } + message_free(&msg_array[i]); + } + xfree(msg_array); + if (n_point != wp_array_i) { + fatal(MYNAME ": track point count mismatch, expected %u, got %u\n", n_point, wp_array_i); + } + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": read track '%s' %u points\n", qPrintable(track->rte_name), n_point); + } + for (i = 0; i < n_point; i++) { + track_add_wpt(track, wp_array[i]); + } + track_add_head(track); + xfree(wp_array); +} + +static void +read_tracks(void) +{ + message_t m; + message_t* msg_array; + unsigned msg_array_n; + route_head** track_array; + unsigned total; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + // get number of tracks + for (;;) { + m.size = 0; + message_write(MSG_TRACK_COUNT, &m); + if (message_read(MSG_TRACK_COUNT, &m)) { + break; + } + if (--attempt == 0) { + fatal(MYNAME ": reading track count failed\n"); + } + } + total = le_readu32(m.data); + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": %u tracks\n", total); + } + if (total == 0) { + message_free(&m); + return; + } + + // First get track headers, then request each track with non-zero number of points + attempt = ATTEMPT_MAX; + for (;;) { + m.size = MSG_REQUEST_TRACKS_SIZE; + memset(m.data, 0, m.size); + ((char*)m.data)[0] = 2; // Download all track headers + message_write(MSG_REQUEST_TRACKS, &m); + if (get_batch(&msg_array, &msg_array_n)) { + break; + } + if (--attempt == 0) { + fatal(MYNAME ": reading track headers failed\n"); + } + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": timed out reading track headers, retrying\n"); + } + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + track_array = (route_head**) xcalloc(total, sizeof(*track_array)); + for (i = 0; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_TRACK_HEADER_OUT) { + const msg_track_header_t* p = (msg_track_header_t*) msg_array[i].data; + if (le_readu32(p->total_points)) { + track_array[i] = route_head_alloc(); + track_array[i]->rte_name = p->name; + } + } else { + fatal(MYNAME ": unexpected message %x while reading track headers\n", id); + } + message_free(&msg_array[i]); + } + xfree(msg_array); + // get each track + for (i = 0; i < total; i++) { + if (track_array[i]) { + read_track(track_array[i]); + } + } + xfree(track_array); +} + +//----------------------------------------------------------------------------- +// Track writing + +static void +write_track_points(void) +{ + message_t m; + const unsigned pt_per_msg = 10; + msg_track_point_t* p = NULL; + unsigned i = 0; + unsigned j = 0; + + do { + const Waypoint* wp = wp_array[i]; + float f; + + if (j == 0) { + message_init_size(&m, 9 + 23 * pt_per_msg); + p =(msg_track_point_t*) m.data; + le_write32(p->total, waypoint_n); + le_write32(p->index, i + 1); + } + assert(p); + encode_time(wp->GetCreationTime().toTime_t(), &p->point[j].year); + le_write32(p->point[j].latitude, delbin_deg2rad(wp->latitude)); + le_write32(p->point[j].longitude, delbin_deg2rad(wp->longitude)); + f = UNKNOWN_ELEV; + if (wp->altitude > unknown_alt) { + f = wp->altitude; + } + le_write_float(p->point[j].elevation, f); + f = WAYPT_GET(wp, speed, 0); + f *= (60 * 60) / 100; + le_write16(p->point[j].speed, (uint16_t)f); + f = WAYPT_GET(wp, course, 0); + f *= 100; + le_write16(p->point[j].heading, (uint16_t)f); + switch (wp->fix) { + default: + p->point[j].status = 0; + break; + case fix_none: + p->point[j].status = 1; + break; + case fix_2d: + p->point[j].status = 2; + break; + case fix_3d: + p->point[j].status = 3; + break; + case fix_dgps: + p->point[j].status = 4 | 3; + break; + } + if (wp->wpt_flags.new_trkseg) { + p->point[j].status |= 0x10; + } + i++; + j++; + if (j == pt_per_msg || i == waypoint_n) { + p->number = j; + m.size = 9 + 23 * j; + add_to_batch(MSG_TRACK_POINT_IN, &m); + j = 0; + } + } while (i < waypoint_n); +} + +static void +write_track_begin(const route_head* track) +{ + waypoint_i = 0; + waypoint_n = track->rte_waypt_ct; + if (waypoint_n) { + wp_array = (Waypoint**) xmalloc(waypoint_n * sizeof(*wp_array)); + } +} + +static void +write_track_point(const Waypoint* wp) +{ + wp_array[waypoint_i++] = (Waypoint*)wp; +} + +static void +write_track_end(const route_head* track) +{ + message_t m; + msg_track_header_in_t* p; + unsigned comment_size = 0; + + if (waypoint_n == 0) { + return; + } + if (!track->rte_desc.isEmpty()) { + comment_size = strlen(CSTRc(track->rte_desc)) + 1; + } + message_init_size(&m, sizeof(msg_track_header_in_t) - 1 + comment_size); + p = (msg_track_header_in_t*) m.data; + memset(p->name, 0, sizeof(p->name)); + if (!track->rte_name.isEmpty()) { + strncpy(p->name, CSTRc(track->rte_name), sizeof(p->name) - 1); + } else { + sprintf(p->name, "%lu", (long)wp_array[0]->GetCreationTime().toTime_t()); + } + le_write32(p->total_points, waypoint_n); + encode_time(current_time().toTime_t(), &p->year); + le_write16(p->color, track_color_index(track->line_color.bbggrr)); + le_write16(p->comment_size, comment_size); + if (comment_size) { + memcpy(p->comment, CSTRc(track->rte_desc), comment_size); + } + add_to_batch(MSG_TRACK_HEADER_IN, &m); + write_track_points(); + send_batch(); + xfree(wp_array); +} + +static void +write_tracks(void) +{ + track_disp_all(write_track_begin, write_track_end, write_track_point); +} + +//----------------------------------------------------------------------------- +// Route reading + +static void +decode_route_shape(const void* data, unsigned* wp_array_i) +{ + const msg_route_shape_t* p = (msg_route_shape_t*) data; + const unsigned n = p->number; + unsigned i; + unsigned j = *wp_array_i; + + for (i = 0; i < n; i++, j++) { + char buf[32]; + Waypoint* wp = new Waypoint; + wp_array[j] = wp; + wp->latitude = delbin_rad2deg(le_read32(p->point[i].latitude)); + wp->longitude = delbin_rad2deg(le_read32(p->point[i].longitude)); + sprintf(buf, "SHP%03u", j); + wp->shortname = buf; + } + *wp_array_i = j; +} + +static Waypoint* +decode_route_point(const void* data) +{ + const msg_route_point_t* p = (const msg_route_point_t*) data; + const char* s = NULL; + gbfile* fd = gbfopen(NULL, "w", MYNAME); + Waypoint* wp = new Waypoint; + if (p->name[0]) { + wp->shortname = p->name; + } + // give these a higher priority than the shape points + wp->route_priority = 1; + wp->latitude = delbin_rad2deg(le_read32(p->latitude)); + wp->longitude = delbin_rad2deg(le_read32(p->longitude)); + switch (p->itinerary_type) { + case 1: + s = "Start"; + break; + case 2: + s = "Stop"; + break; + case 3: + s = "Finish"; + break; + case 4: + s = "Via"; + break; + case 5: + s = "Via Hidden"; + break; + case 6: + switch (p->turn_type) { + case 1: + s = "Turn, Straight"; + break; + case 2: + s = "Turn, Right"; + break; + case 3: + s = "Turn, Bear Right"; + break; + case 4: + s = "Turn, Keep Right"; + break; + case 5: + s = "Turn, Left"; + break; + case 6: + s = "Turn, Bear Left"; + break; + case 7: + s = "Turn, Keep Left"; + break; + case 8: + s = "Turn, Reverse Direction"; + break; + case 9: + s = "Turn, Street Name Change"; + break; + } + break; + } + if (s) { + gbfprintf(fd, "Type: %s", s); + } + if (p->exit_label_size && p->exit_label[0]) { + gbfprintf(fd, "\nExit: %s", p->exit_label); + } + s = p->exit_label + p->exit_label_size; + if (s[0] && s[1]) { + gbfprintf(fd, "\n%s", s + 1); + } + if (fd->memlen) { + gbfputc(0, fd); +#if NEW_STRINGS + // Reconsider if there's a less grubby way to do this. + wp->notes = QString::fromUtf8((const char*) fd->handle.mem, fd->memlen); +#else + wp->notes = (char*) xmalloc(fd->memlen); + memcpy(wp->notes, fd->handle.mem, fd->memlen); +#endif + } + gbfclose(fd); + return wp; +} + +static void +read_route(route_head* route) +{ + message_t m; + message_t* msg_array; + const msg_route_header_t* p; + unsigned msg_array_n; + unsigned wp_array_i = 0; + unsigned route_total, shape_total, total; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + for (;;) { + m.size = MSG_REQUEST_ROUTES_SIZE; + memset(m.data, 0, m.size); + ((char*)m.data)[0] = 1; // Download single route + strcpy((char*)m.data + 1, CSTRc(route->rte_name)); + message_write(MSG_REQUEST_ROUTES, &m); + if (get_batch(&msg_array, &msg_array_n)) { + break; + } + if (--attempt == 0) { + fatal(MYNAME ": reading route '%s' failed (timed out)\n", qPrintable(route->rte_name)); + } + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": timed out reading route route '%s', retrying\n", qPrintable(route->rte_name)); + } + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + if (msg_array_n == 0 || message_get_id(&msg_array[0]) != MSG_ROUTE_HEADER_OUT) { + fatal(MYNAME ": missing route header\n"); + } + p = (const msg_route_header_t*) msg_array[0].data; + route_total = le_readu32(p->total_route_point); + shape_total = le_readu32(p->total_shape_point); + total = route_total + shape_total; + wp_array = (Waypoint**) xcalloc(total, sizeof(*wp_array)); + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": route '%s' %u points, %u shape points\n", + qPrintable(route->rte_name), route_total, shape_total); + } + message_free(&msg_array[0]); + for (i = 1; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_ROUTE_POINT_OUT) { + wp_array[wp_array_i] = decode_route_point(msg_array[i].data); + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": route point '%s'\n", qPrintable(wp_array[wp_array_i]->shortname)); + } + wp_array_i++; + } else if (id == MSG_ROUTE_SHAPE_OUT) { + decode_route_shape(msg_array[i].data, &wp_array_i); + } else { + fatal(MYNAME ": unexpected message %x while reading route '%s'\n", id, qPrintable(route->rte_name)); + } + message_free(&msg_array[i]); + } + xfree(msg_array); + if (total != wp_array_i) { + fatal(MYNAME ": route point count mismatch, expected %u, got %u\n", total, wp_array_i); + } + for (i = 0; i < total; i++) { + route_add_wpt(route, wp_array[i]); + } + xfree(wp_array); + route_add_head(route); +} + +static void +read_routes(void) +{ + message_t m; + message_t* msg_array; + unsigned msg_array_n; + route_head** route_array; + unsigned total; + unsigned i; + int attempt = ATTEMPT_MAX; + + message_init(&m); + // get number of routes + for (;;) { + m.size = 0; + message_write(MSG_ROUTE_COUNT, &m); + if (message_read(MSG_ROUTE_COUNT, &m)) { + break; + } + if (--attempt == 0) { + fatal(MYNAME ": reading route count failed\n"); + } + } + total = le_readu32(m.data); + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": %u routes\n", total); + } + if (total == 0) { + message_free(&m); + return; + } + + // First get route headers, then request each route + attempt = ATTEMPT_MAX; + for (;;) { + m.size = MSG_REQUEST_ROUTES_SIZE; + memset(m.data, 0, m.size); + ((char*)m.data)[0] = 2; // Download all route headers + message_write(MSG_REQUEST_ROUTES, &m); + if (get_batch(&msg_array, &msg_array_n)) { + break; + } + if (--attempt == 0) { + fatal(MYNAME ": reading route headers failed\n"); + } + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": timed out reading route headers, retrying\n"); + } + m.size = MSG_BREAK_SIZE; + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + } + message_free(&m); + route_array = (route_head**) xcalloc(total, sizeof(*route_array)); + for (i = 0; i < msg_array_n; i++) { + unsigned id = message_get_id(&msg_array[i]); + if (id == MSG_ROUTE_HEADER_OUT) { + route_array[i] = route_head_alloc(); + route_array[i]->rte_name = ((msg_route_header_t*)msg_array[i].data)->name; + } else { + fatal(MYNAME ": unexpected message %x while reading route headers\n", id); + } + message_free(&msg_array[i]); + } + xfree(msg_array); + // get each route + for (i = 0; i < total; i++) { + read_route(route_array[i]); + } + xfree(route_array); +} + +//----------------------------------------------------------------------------- +// Route writing + +static unsigned route_point_n; +static unsigned shape_point_n; +static unsigned* shape_point_counts; + +static void +write_route_shape_points(Waypoint** array, unsigned n) +{ + message_t m; + const unsigned pt_per_msg = 25; + msg_route_shape_t* p = NULL; + unsigned i = 0; + unsigned j = 0; + + do { + if (j == 0) { + message_init_size(&m, 10 + 8 * pt_per_msg); + p = (msg_route_shape_t*) m.data; + le_write32(p->total, n); + le_write32(p->index, i + 1); + p->reserved = 0; + } + assert(p); + le_write32(p->point[j].latitude, delbin_deg2rad(array[i]->latitude)); + le_write32(p->point[j].longitude, delbin_deg2rad(array[i]->longitude)); + i++; + j++; + if (j == pt_per_msg || i == n) { + p->number = j; + m.size = 10 + 8 * j; + add_to_batch(MSG_ROUTE_SHAPE_IN, &m); + j = 0; + } + } while (i < n); +} + +static void +write_route_points(void) +{ + unsigned route_point_i = 0; + unsigned i = 0; + + while (i < waypoint_n) { + message_t m; + unsigned shape_n; + const Waypoint* wp = wp_array[i]; + msg_route_point_t* p; + char* s; + + message_init_size(&m, sizeof(msg_route_point_t) + 1 + 1 + 4); + p = (msg_route_point_t*) m.data; + memset(m.data, 0, m.size); + route_point_i++; + shape_n = shape_point_counts[route_point_i]; + le_write32(p->total, route_point_n); + le_write32(p->index, route_point_i); + if (!wp->shortname.isEmpty()) { + strncpy(p->name, CSTRc(wp->shortname), sizeof(p->name) - 1); + } else { + sprintf(p->name, "RPT%u", route_point_i); + } + le_write32(p->latitude, delbin_deg2rad(wp->latitude)); + le_write32(p->longitude, delbin_deg2rad(wp->longitude)); + p->exit_label_size = 1; + s = p->exit_label + p->exit_label_size; + s[0] = 1; // comment size + le_write32(s + 2, shape_n); + if (route_point_i == 1) { + p->itinerary_type = 1; // start + } else if (route_point_i == route_point_n) { + p->itinerary_type = 3; // finish + } + add_to_batch(MSG_ROUTE_POINT_IN, &m); + i++; + if (shape_n) { + write_route_shape_points(&wp_array[i], shape_n); + i += shape_n; + } + } +} + +static void +write_route_begin(const route_head* track) +{ + waypoint_i = 0; + route_point_n = 0; + shape_point_n = 0; + waypoint_n = track->rte_waypt_ct; + if (waypoint_n) { + wp_array = (Waypoint**) xmalloc(waypoint_n * sizeof(*wp_array)); + shape_point_counts = (unsigned int*) xcalloc(waypoint_n, sizeof(*shape_point_counts)); + } +} + +static void +write_route_point(const Waypoint* wp) +{ + wp_array[waypoint_i++] = (Waypoint*)wp; + if (wp->shortname.startsWith("SHP")) { + shape_point_n++; + shape_point_counts[route_point_n]++; + } else { + route_point_n++; + } +} + +static void +write_route_end(const route_head* route) +{ + message_t m; + msg_route_header_in_t* p; + + if (waypoint_n == 0) { + return; + } + message_init_size(&m, sizeof(msg_route_header_in_t)); + p = (msg_route_header_in_t*) m.data; + memset(p->name, 0, sizeof(p->name)); + if (!route->rte_name.isEmpty()) { + strncpy(p->name, CSTRc(route->rte_name), sizeof(p->name) - 1); + } else { + sprintf(p->name, "%lu", (long)wp_array[0]->GetCreationTime().toTime_t()); + } + p->type = 0; + le_write32(p->total_route_point, route_point_n); + le_write32(p->total_shape_point, shape_point_n); + add_to_batch(MSG_ROUTE_HEADER_IN, &m); + write_route_points(); + send_batch(); + if (wp_array) { + xfree(wp_array); + xfree(shape_point_counts); + } +} + +static void +write_routes(void) +{ + route_disp_all(write_route_begin, write_route_end, write_route_point); +} + +//----------------------------------------------------------------------------- +// Current position + +static Waypoint* +decode_navmsg(const void* data) +{ + Waypoint* wp = new Waypoint; + const msg_navigation_t* p = (const msg_navigation_t*) data; + struct tm t; + + t.tm_year = le_readu16(p->year) - 1900; + t.tm_mon = p->month - 1; + t.tm_mday = p->day; + t.tm_hour = p->hour; + t.tm_min = p->minute; + t.tm_sec = p->second; + wp->SetCreationTime(mkgmtime(&t)); + wp->sat = p->satellites; + wp->latitude = le_read_double(p->latitude); + wp->longitude = le_read_double(p->longitude); + wp->altitude = le_read_double(p->elevation); + wp->speed = le_read_float(p->speed); + wp->speed *= (1000.0f / (60 * 60)); + wp->wpt_flags.speed = 1; + wp->course = le_readu16(p->heading); + wp->course /= 100; + wp->wpt_flags.course = 1; + decode_sat_fix(wp, p->fix_status); + wp->shortname = "Position"; + return wp; +} + +static Waypoint* +read_position(void) +{ + Waypoint* wp; + message_t m; + + message_init(&m); + message_read(MSG_NAVIGATION, &m); + wp = decode_navmsg(m.data); + if (wp->fix > fix_none && + message_read_1(MSG_SATELLITE_INFO, &m) == MSG_SATELLITE_INFO) { + const msg_satellite_t* p = (const msg_satellite_t*) m.data; + wp->hdop = le_readu16(p->hdop); + wp->hdop /= 100; + wp->vdop = le_readu16(p->vdop); + wp->vdop /= 100; + wp->pdop = le_readu16(p->pdop); + wp->pdop /= 100; + } + message_free(&m); + return wp; +} + +//----------------------------------------------------------------------------- + +static void +delbin_list_units() +{ + int i; + for (i = 0; i < n_delbin_units; i++) { + printf("%u %s %s\n", + delbin_unit_info[i].unit_number, + delbin_unit_info[i].unit_serial_number, + delbin_unit_info[i].unit_name); + } +} + +static void +delbin_rw_init(const QString& fname) +{ + message_t m; + char buf[256]; + + if (!mkshort_handle) { + mkshort_handle = mkshort_new_handle(); + } + // Contrary to doc, it looks like there's a limit of 32 bytes + // and a null terminator is required, at least in F/W 2.6.210726 + // on a PN-40. + setshort_length(mkshort_handle, 31); + setshort_whitespace_ok(mkshort_handle, 1); + setshort_badchars(mkshort_handle, ""); + setshort_mustuniq(mkshort_handle, 1); + + delbin_os_ops.init(fname); + + // Often the first packet is part of an old message, sometimes it can + // confuse the first message read if we don't get rid of it + packet_read(buf); + // Send a break to clear any state from a previous failure + message_init_size(&m, MSG_BREAK_SIZE); + memset(m.data, 0, m.size); + message_write(MSG_BREAK, &m); + // get version info + m.size = 0; + message_write(MSG_VERSION, &m); + if (message_read(MSG_VERSION, &m)) { + const msg_version_t* p = (const msg_version_t*) m.data; + if (global_opts.debug_level >= DBGLVL_L) { + warning(MYNAME ": device %s %s\n", p->product, p->firmware); + } + if (opt_long_notes) { + use_extended_notes = TRUE; + } else if (strstr(p->product, "PN-20")) { + use_extended_notes = p->firmware[0] > '1' || + (p->firmware[0] == '1' && p->firmware[2] >= '6'); + } else if (strstr(p->product, "PN-30") || strstr(p->product, "PN-40")) { + use_extended_notes = p->firmware[0] > '2' || + (p->firmware[0] == '2' && p->firmware[2] >= '5'); + } else { + // assume PN-60 or later + use_extended_notes = TRUE; + } + delbin_unit_info[n_delbin_units].unit_number = n_delbin_units; + delbin_unit_info[n_delbin_units].unit_serial_number = xstrndup(p->serial, sizeof(p->serial)); + delbin_unit_info[n_delbin_units].unit_name = xstrndup(p->product, sizeof(p->product)); + n_delbin_units++; + } + message_free(&m); + + if (fname.length() > 4) { + if (fname.mid(4,4) == "list") { + delbin_list_units(); + exit(1); + } + } +} + +static void +delbin_rw_deinit(void) +{ + if (mkshort_handle) { + mkshort_del_handle(&mkshort_handle); + } + delbin_os_ops.deinit(); +} + +static void +delbin_read(void) +{ + if (doing_wpts) { + if (opt_getposn) { + waypt_add(read_position()); + } else { + read_waypoints(); + } + } + if (doing_trks) { + read_tracks(); + } + if (doing_rtes) { + read_routes(); + } +} + +static void +delbin_write(void) +{ + if (doing_wpts) { + message_t m; + device_max_waypoint = 1000; + message_init_size(&m, 0); + message_write(MSG_CAPABILITIES, &m); + if (message_read(MSG_CAPABILITIES, &m)) { + const msg_capabilities_t* p = (const msg_capabilities_t*) m.data; + device_max_waypoint = le_readu32(p->max_waypoints); + } + message_free(&m); + + if (opt_nuke_wpt) { + add_nuke(nuke_type_wpt); + } + write_waypoints(); + } + if (doing_trks) { + if (opt_nuke_trk) { + add_nuke(nuke_type_trk); + } + write_tracks(); + } + if (doing_rtes) { + if (opt_nuke_rte) { + add_nuke(nuke_type_rte); + } + write_routes(); + } +} + +static Waypoint* +delbin_rd_position(posn_status* status) +{ + return read_position(); +} + +ff_vecs_t delbin_vecs = { + ff_type_serial, + FF_CAP_RW_ALL, + delbin_rw_init, + delbin_rw_init, + delbin_rw_deinit, + delbin_rw_deinit, + delbin_read, + delbin_write, + NULL, + delbin_args, + CET_CHARSET_LATIN1, 1, + { delbin_rw_init, delbin_rd_position, delbin_rw_deinit } +}; + +//============================================================================= +// OS device I/O implementations + +#define VENDOR_ID 0x1163 +#define PRODUCT_ID 0x2020 + +//----------------------------------------------------------------------------- +// Windows +#ifdef HAVE_WDK + +#undef HAVE_LIBUSB + +#define WIN32_LEAN_AND_MEAN +#include +#include +// If hidsdi.h is not found, you need to download the Windows Driver Kit, +// from http://www.microsoft.com/whdc/Devtools/wdk/default.mspx +// You need to install 'build environments' and 'tools' from the SDK and +// follow the instructions in the Install.html to get MSVC to find the right +// headers and libraries. +#include +#include + +static HANDLE hid_handle; + +static void +win_os_init(const QString& fname) +{ + GUID hid_guid; + HDEVINFO dev_info; + SP_DEVICE_INTERFACE_DATA dev_int_data; + PHIDP_PREPARSED_DATA hid_ppd; + HIDP_CAPS hid_caps; + const char* busy = ""; + unsigned i; + + hid_handle = INVALID_HANDLE_VALUE; + HidD_GetHidGuid(&hid_guid); + dev_info = SetupDiGetClassDevs(&hid_guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (dev_info == INVALID_HANDLE_VALUE) { + fatal(MYNAME ": SetupDiGetClassDevs failed %u\n", GetLastError()); + } + dev_int_data.cbSize = sizeof(dev_int_data); + for (i = 0; SetupDiEnumDeviceInterfaces(dev_info, NULL, &hid_guid, i, &dev_int_data); i++) { + union { + SP_DEVICE_INTERFACE_DETAIL_DATA detail_data; + char buf[300]; + } u; + u.detail_data.cbSize = sizeof(u.detail_data); + if (SetupDiGetDeviceInterfaceDetail(dev_info, + &dev_int_data, &u.detail_data, sizeof(u.buf), NULL, NULL)) { + HANDLE h = CreateFile(u.detail_data.DevicePath, + FILE_READ_DATA | FILE_WRITE_DATA, 0, NULL, OPEN_EXISTING, 0, NULL); + if (h != INVALID_HANDLE_VALUE) { + HIDD_ATTRIBUTES hid_attr; + hid_attr.Size = sizeof(hid_attr); + if (HidD_GetAttributes(h, &hid_attr) && + hid_attr.VendorID == VENDOR_ID && hid_attr.ProductID == PRODUCT_ID) { + hid_handle = h; + break; + } + CloseHandle(h); + } else if (GetLastError() == ERROR_SHARING_VIOLATION && + strstr(u.detail_data.DevicePath, "1163") && + strstr(u.detail_data.DevicePath, "2020")) { + busy = " (device busy?)"; + } + } + } + SetupDiDestroyDeviceInfoList(dev_info); + if (hid_handle == INVALID_HANDLE_VALUE) { + fatal(MYNAME ": no DeLorme PN found%s\n", busy); + } + if (!HidD_GetPreparsedData(hid_handle, &hid_ppd)) { + fatal(MYNAME ": HidD_GetPreparsedData failed %u\n", GetLastError()); + } + if (!HidP_GetCaps(hid_ppd, &hid_caps)) { + fatal(MYNAME ": HidP_GetCaps failed %u\n", GetLastError()); + } + // report length includes report id + delbin_os_packet_size = hid_caps.InputReportByteLength - 1; + HidD_FreePreparsedData(hid_ppd); +} + +static void +win_os_deinit(void) +{ + CloseHandle(hid_handle); +} + +static unsigned +win_os_packet_read(void* buf) +{ + DWORD n; + char buf1[257]; + // first byte is report id + if (ReadFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) { + unsigned err = GetLastError(); + fatal(MYNAME ": ReadFile failed %u\n", err); + } + if (n > 0) { + n--; + } + memcpy(buf, buf1 + 1, n); + return n; +} + +static unsigned +win_os_packet_write(const void* buf, unsigned size) +{ + DWORD n; + char buf1[257]; + // first byte is report id + buf1[0] = 0; + memcpy(buf1 + 1, buf, size); + if (WriteFile(hid_handle, buf1, delbin_os_packet_size + 1, &n, NULL) == 0) { + unsigned err = GetLastError(); + fatal(MYNAME ": WriteFile of %u bytes failed with %u. Size: %u Wrote: %d\n", + delbin_os_packet_size + 1, err, size, (int) n); + } + if (n > size) { + n = size; + } + return n; +} + +delbin_os_ops_t delbin_os_ops = { + win_os_init, + win_os_deinit, + win_os_packet_read, + win_os_packet_write +}; + +#endif // HAVE_WDK + +//----------------------------------------------------------------------------- +// MacOS X +#if __APPLE__ + +#undef HAVE_LIBUSB + +#include +#include +#include +#include +#include + +// IOHIDDeviceInterface121::getReport() does not work, it hangs the process +// in some sort of unkillable state. So reading is done via a separate thread +// with a run loop and interrupt report callback. Yuck. + +static IOHIDDeviceInterface122** device; +static pthread_t thread; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; +static char* report_buf; +static char* packet_array[32]; +static unsigned packet_array_head; +static unsigned packet_array_tail; +static CFRunLoopRef run_loop; + +static void* +thread_func(void* run_loop_source) +{ + run_loop = CFRunLoopGetCurrent(); +#if __cplusplus + CFRunLoopAddSource(run_loop, (__CFRunLoopSource*) run_loop_source, kCFRunLoopDefaultMode); +#else + CFRunLoopAddSource(run_loop, run_loop_source, kCFRunLoopDefaultMode); +#endif + CFRunLoopRun(); + return NULL; +} + +static void +interrupt_report_cb(void* target, IOReturn result, void* refcon, void* sender, UInt32 bufferSize) +{ + memcpy(packet_array[packet_array_head], report_buf, delbin_os_packet_size); + pthread_mutex_lock(&mutex); + if (packet_array_head == packet_array_tail) { + pthread_cond_signal(&cond); + } + packet_array_head++; + packet_array_head &= sizeofarray(packet_array) - 1; + if (packet_array_head == packet_array_tail && global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": packet_array overrun, packets lost\n"); + } + pthread_mutex_unlock(&mutex); +} + +static void +mac_os_init(const QString& fname) +{ + CFMutableDictionaryRef dict = IOServiceMatching(kIOHIDDeviceKey); + io_service_t service; + IOCFPlugInInterface** plugin; + CFNumberRef cf_num; + CFRunLoopSourceRef run_loop_source; + int i; + kern_return_t kr; + HRESULT hr; + IOReturn ir; + SInt32 unused; + + i = VENDOR_ID; + cf_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i); + CFDictionaryAddValue(dict, CFSTR(kIOHIDVendorIDKey), cf_num); + CFRelease(cf_num); + i = PRODUCT_ID; + cf_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &i); + CFDictionaryAddValue(dict, CFSTR(kIOHIDProductIDKey), cf_num); + CFRelease(cf_num); + service = IOServiceGetMatchingService(kIOMasterPortDefault, dict); + if (service == 0) { + fatal(MYNAME ": no DeLorme PN found\n"); + } + kr = IOCreatePlugInInterfaceForService( + service, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &unused); + if (kr) { + fatal(MYNAME ": IOCreatePlugInInterfaceForService failed 0x%x\n", (int)kr); + } + IOObjectRelease(service); + hr = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID122), (void**)&device); + if (hr) { + fatal(MYNAME ": QueryInterface failed 0x%x\n", (int)hr); + } + (*plugin)->Release(plugin); + ir = (*device)->open(device, kIOHIDOptionsTypeSeizeDevice); + if (ir) + fatal(MYNAME ": device open failed 0x%x - %s\n", (int)ir, + mach_error_string(ir)); + ir = (*device)->createAsyncEventSource(device, &run_loop_source); + if (ir) { + fatal(MYNAME ": createAsyncEventSource failed 0x%x\n", (int)ir); + } + delbin_os_packet_size = 64; + report_buf = (char*)xmalloc(delbin_os_packet_size); + for (i = sizeofarray(packet_array); i--;) { + packet_array[i] = (char*)xmalloc(delbin_os_packet_size); + } + ir = (*device)->setInterruptReportHandlerCallback( + device, report_buf, delbin_os_packet_size, interrupt_report_cb, NULL, NULL); + if (ir) { + fatal(MYNAME ": setInterruptReportHandlerCallback failed 0x%x\n", (int)ir); + } + i = pthread_create(&thread, NULL, thread_func, run_loop_source); + if (i) { + fatal(MYNAME ": pthread_create failed %d\n", i); + } +} + +static void +mac_os_deinit(void) +{ + void* unused; + unsigned i; + CFRunLoopStop(run_loop); + pthread_join(thread, &unused); + (*device)->Release(device); + xfree(report_buf); + for (i = sizeofarray(packet_array); i--;) { + xfree(packet_array[i]); + } +} + +static unsigned +mac_os_packet_read(void* buf) +{ + pthread_mutex_lock(&mutex); + while (packet_array_head == packet_array_tail) { + pthread_cond_wait(&cond, &mutex); + } + memcpy(buf, packet_array[packet_array_tail++], delbin_os_packet_size); + packet_array_tail &= sizeofarray(packet_array) - 1; + pthread_mutex_unlock(&mutex); + return delbin_os_packet_size; +} + +static unsigned +mac_os_packet_write(const void* buf, unsigned size) +{ + IOReturn r = (*device)->setReport( + device, kIOHIDReportTypeOutput, 0, (void*)buf, size, 2000, NULL, NULL, NULL); + if (r) { + fatal("setReport failed 0x%x\n", (int)r); + } + return size; +} + +delbin_os_ops_t delbin_os_ops = { + mac_os_init, + mac_os_deinit, + mac_os_packet_read, + mac_os_packet_write +}; + +#endif // __APPLE__ + +//----------------------------------------------------------------------------- +// libusb +#if HAVE_LIBUSB + +#include + +static struct usb_device* usb_dev; +static usb_dev_handle* usb_handle; +static int endpoint_in; +static int endpoint_out; + +static void +libusb_os_init(const QString& fname) +{ + struct usb_bus* bus; + const struct usb_endpoint_descriptor* endpoint_desc; + + usb_init(); + usb_find_busses(); + usb_find_devices(); + for (bus = usb_busses; usb_dev == NULL && bus; bus = bus->next) { + struct usb_device* d; + for (d = bus->devices; d; d = d->next) { + if (d->descriptor.idVendor == VENDOR_ID && d->descriptor.idProduct == PRODUCT_ID) { + usb_dev = d; + break; + } + } + } + if (usb_dev == NULL) { + fatal(MYNAME ": no DeLorme PN found\n"); + } + usb_handle = usb_open(usb_dev); + if (usb_handle == NULL) { + fatal(MYNAME ": %s\n", usb_strerror()); + } + + // Device has 1 configuration, 1 interface, 2 interrupt endpoints + if (usb_claim_interface(usb_handle, 0) < 0) { +#if LIBUSB_HAS_DETACH_KERNEL_DRIVER_NP + if (usb_detach_kernel_driver_np(usb_handle, 0) < 0) { + warning(MYNAME ": %s\n", usb_strerror()); + } + if (usb_claim_interface(usb_handle, 0) < 0) +#endif + { + const char* s = usb_strerror(); + usb_close(usb_handle); + fatal(MYNAME ": %s\n", s); + } + } + endpoint_desc = usb_dev->config[0].interface[0].altsetting[0].endpoint; + delbin_os_packet_size = endpoint_desc[0].wMaxPacketSize; + endpoint_in = endpoint_desc[0].bEndpointAddress; + endpoint_out = endpoint_desc[1].bEndpointAddress; + if ((endpoint_in & USB_ENDPOINT_DIR_MASK) == USB_ENDPOINT_OUT) { + int t = endpoint_in; + endpoint_in = endpoint_out; + endpoint_out = t; + } +} + +static void +libusb_os_deinit(void) +{ + usb_release_interface(usb_handle, 0); + usb_close(usb_handle); +} + +static unsigned +libusb_os_packet_read(void* buf) +{ + int n = usb_interrupt_read(usb_handle, endpoint_in, (char*) buf, delbin_os_packet_size, 2000); + if (n < 0) { + fatal(MYNAME ": %s\n", usb_strerror()); + } + return n; +} + +static unsigned +libusb_os_packet_write(const void* buf, unsigned size) +{ + int n = usb_interrupt_write(usb_handle, endpoint_out, (char*)buf, size, 2000); + if (n < 0) { + fatal(MYNAME ": %s\n", usb_strerror()); + } + return n; +} + +#if HAVE_LINUX_HID +static const delbin_os_ops_t libusb_os_ops = +#else +delbin_os_ops_t delbin_os_ops = +#endif +{ + libusb_os_init, + libusb_os_deinit, + libusb_os_packet_read, + libusb_os_packet_write +}; + +#endif // HAVE_LIBUSB + +//----------------------------------------------------------------------------- +// Linux +#if HAVE_LINUX_HID + +#include +#include +#include +#include +#include +#include +#include +#include + +// Read from hidraw, write to hiddev. Reading from hiddev does not work, +// and neither does writing to hidraw. +static int fd_hidraw; +static int fd_hiddev; + +static int linuxhid_os_init_status; + +static void +linuxhid_os_init(const QString& fname) +{ + struct hidraw_devinfo info; + struct hiddev_field_info finfo; + DIR* dir = NULL; + struct dirent* d; + + fd_hidraw = fd_hiddev = -1; + if (fname.startsWith("hid:")) { + char* raw_name = xstrdup(qPrintable(fname.mid(4))); + char* dev_name = strchr(raw_name, ','); + if (dev_name == NULL) { + fatal(MYNAME ": missing hiddev path\n"); + } + *dev_name++ = 0; + fd_hidraw = open(raw_name, O_RDONLY); + if (fd_hidraw < 0) { + fatal(MYNAME ": %s: %s\n", raw_name, strerror(errno)); + } + fd_hiddev = open(dev_name, O_WRONLY); + if (fd_hiddev < 0) { + fatal(MYNAME ": %s: %s\n", dev_name, strerror(errno)); + } + xfree(raw_name); + } else { + dir = opendir("/dev"); + } + while (dir && (d = readdir(dir)) != NULL) { + if (strncmp(d->d_name, "hidraw", 6) == 0) { + int fd1, fd2; + char raw_name[32]; + char dev_name[32]; + sprintf(raw_name, "/dev/%s", d->d_name); + fd1 = open(raw_name, O_RDONLY); + if (fd1 < 0) { + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": %s: %s\n", raw_name, strerror(errno)); + } + continue; + } + sprintf(dev_name, "/dev/usb/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1); + fd2 = open(dev_name, O_WRONLY); + if (fd2 < 0 && errno == ENOENT) { + sprintf(dev_name, "/dev/hiddev%s", raw_name + sizeof("/dev/hidraw") - 1); + fd2 = open(dev_name, O_WRONLY); + } + if (fd2 < 0) { + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": %s: %s\n", dev_name, strerror(errno)); + } + close(fd1); + continue; + } + if (ioctl(fd1, HIDIOCGRAWINFO, &info) == 0 && + info.vendor == VENDOR_ID && info.product == PRODUCT_ID) { + fd_hidraw = fd1; + fd_hiddev = fd2; + break; + } + close(fd1); + close(fd2); + } + } + if (dir) { + closedir(dir); + } + if (fd_hidraw < 0) { + if (linuxhid_os_init_status == 0) { + fatal(MYNAME ": no DeLorme PN found\n"); + } + return; + } + finfo.report_type = HID_REPORT_TYPE_INPUT; + finfo.report_id = 0; + finfo.field_index = 0; + if (ioctl(fd_hiddev, HIDIOCGFIELDINFO, &finfo) < 0) { + warning(MYNAME ": HIDIOCGFIELDINFO: %s\n", strerror(errno)); + if (linuxhid_os_init_status == 0) { + exit(1); + } + return; + } + delbin_os_packet_size = finfo.maxusage; + linuxhid_os_init_status = 0; +} + +static void +linuxhid_os_deinit(void) +{ + close(fd_hidraw); + close(fd_hiddev); +} + +static unsigned +linuxhid_os_packet_read(void* buf) +{ + int n = read(fd_hidraw, buf, delbin_os_packet_size); + if (n < 0) { + fatal(MYNAME ": %s\n", strerror(errno)); + } + return n; +} + +static unsigned +linuxhid_os_packet_write(const void* buf, unsigned size) +{ + struct hiddev_usage_ref_multi urefm; + struct hiddev_report_info rinfo; + const uint8_t* p = (const uint8_t*) buf; + unsigned i; + + for (i = 0; i < size; i++) { + urefm.values[i] = p[i]; + } + urefm.num_values = size; + memset(&urefm.uref, 0, sizeof(urefm.uref)); + urefm.uref.report_type = HID_REPORT_TYPE_OUTPUT; + if (ioctl(fd_hiddev, HIDIOCSUSAGES, &urefm)) { + fatal(MYNAME ": HIDIOCSUSAGES: %s\n", strerror(errno)); + } + memset(&rinfo, 0, sizeof(rinfo)); + rinfo.report_type = HID_REPORT_TYPE_OUTPUT; + if (ioctl(fd_hiddev, HIDIOCSREPORT, &rinfo)) { + fatal(MYNAME ": HIDIOCSREPORT: %s\n", strerror(errno)); + } + return size; +} + +static const delbin_os_ops_t linuxhid_os_ops = { + linuxhid_os_init, + linuxhid_os_deinit, + linuxhid_os_packet_read, + linuxhid_os_packet_write +}; + +static void +linux_os_init(const QString& fname) +{ + // tell linuxhid_os_init not to exit + linuxhid_os_init_status = 1; + linuxhid_os_init(fname); + if (linuxhid_os_init_status == 0) { + delbin_os_ops = linuxhid_os_ops; + } else { +#if HAVE_LIBUSB + if (global_opts.debug_level >= DBGLVL_M) { + warning(MYNAME ": HID init failed, falling back to libusb\n"); + } + delbin_os_ops = libusb_os_ops; + delbin_os_ops.init(fname); +#else + fatal(MYNAME ": no DeLorme PN found\n"); +#endif + } +} + +delbin_os_ops_t delbin_os_ops = { + linux_os_init, + NULL, + NULL, + NULL +}; + +#endif // HAVE_LINUX_HID + +//----------------------------------------------------------------------------- +// stubs +#if !(HAVE_WDK || __APPLE__ || HAVE_LIBUSB || HAVE_LINUX_HID) +static void +stub_os_init(const QString& fname) +{ + fatal(MYNAME ": OS not supported\n"); +} +static void +stub_os_deinit(void) +{ +} +static unsigned +stub_os_packet_read(void* buf) +{ + return 0; +} +static unsigned +stub_os_packet_write(const void* buf, unsigned size) +{ + return 0; +} +delbin_os_ops_t delbin_os_ops = { + stub_os_init, + stub_os_deinit, + stub_os_packet_read, + stub_os_packet_write +}; +#endif +// end OS device I/O implementations section +//============================================================================= + +static const int track_color_bgr[] = { + 0x0000ff, // red + 0x00ffff, // yellow + 0x008000, // green + 0xff0000, // blue + 0x808080, // gray + 0xffffff, // white + 0, // black + 0xffff00, // cyan + 0xff00ff, // magenta + 0x00a5ff, // orange + 0x82004b, // indigo + 0xeea5ee // violet +}; + +static int track_color(unsigned i) +{ + int bgr = -1; + if (i < sizeofarray(track_color_bgr)) { + bgr = track_color_bgr[i]; + } + return bgr; +} + +static unsigned track_color_index(int bgr) +{ + unsigned i = sizeofarray(track_color_bgr); + do { + i--; + } while (i != 0 && track_color_bgr[i] != bgr); + return i; +} + +static const char* const waypoint_symbol_name[] = { + // 0 + "Red Map Pin", + "Dark Red Map Pin", + "Yellow Map Pin", + "Dark Yellow Map Pin", + "Green Map Pin", + "Dark Green Map Pin", + "Turquoise Map Pin", + "Dark Turquoise Map Pin", + "Blue Map Pin", + "Dark Blue Map Pin", + // 10 + "Gray Map Pin", + "Dark Gray Map Pin", + "Red Flag", + "Dark Red Flag", + "Yellow Flag", + "Dark Yellow Flag", + "Green Flag", + "Dark Green Flag", + "Turquoise Flag", + "Dark Turquoise Flag", + // 20 + "Blue Flag", + "Dark Blue Flag", + "Gray Flag", + "Dark Gray Flag", + "Red Dot", + "Dark Red Dot", + "Yellow Dot", + "Dark Yellow Dot", + "Green Dot", + "Dark Green Dot", + // 30 + "Turquoise Dot", + "Dark Turquoise Dot", + "Blue Dot", + "Dark Blue Dot", + "Gray Dot", + "Dark Gray Dot", + "Small Red Dot", + "Small Dark Red Dot", + "Small Yellow Dot", + "Small Dark Yellow Dot", + // 40 + "Small Green Dot", + "Small Dark Green Dot", + "Small Turquoise Dot", + "Small Dark Turquoise Dot", + "Small Blue Dot", + "Small Dark Blue Dot", + "Small Gray Dot", + "Small Dark Gray Dot", + "Arrow Up", + "Arrow Down", + // 50 + "Arrow Left", + "Arrow Right", + "Arrow Up Left", + "Arrow Up Right", + "Arrow Down Left", + "Arrow Down Right", + "Green Star", + "Yellow Square", + "Red X", + "Turquoise Circle", + // 60 + "Purple Triangle", + "American Flag", + "Stop", + "Parking", + "First Aid", + "Dining", + "Railroad Crossing", + "Heliport", + "Restroom", + "Information", + // 70 + "Diver Down", + "Exit", + "Health Facility", + "Police", + "Post Office", + "Mining", + "Danger", + "Money", + "Exclamation", + "Car", + // 80 + "Jeep", + "Truck", + "Tow Truck", + "Motor Home", + "School Bus", + "Four-wheeler", + "Snowmobile", + "Sailboat", + "Powerboat", + "Boat Launch", + // 90 + "Anchor", + "Buoy", + "Shipwreck", + "Glider Area", + "Private Airport", + "Public Airport", + "Military Airport", + "Military Base", + "House", + "Church", + // 100 + "Building", + "School", + "Lighthouse", + "Bridge", + "Radio Tower", + "Dam", + "Tunnel", + "Toll Booth", + "Gas Station", + "Lodging", + // 110 + "Telephone", + "Traffic Light", + "Fire Hydrant", + "Cemetery", + "Picnic Table", + "Tent", + "Shelter", + "Camper", + "Fire", + "Shower", + // 120 + "Drinking Water", + "Binoculars", + "Camera", + "Geocache", + "Geocache Found", + "Fishing Pole", + "Ice Fishing Trap Set", + "Ice Fishing Trap Up", + "Moose", + "Deer", + // 130 + "Bear", + "Bird", + "Duck", + "Fish", + "Deer Tracks", + "Animal Tracks", + "Bird Tracks", + "Birch Tree", + "Evergreen Tree", + "Deciduous Tree", + // 140 + "Flower Garden", + "Mountain", + "Cave", + "Beach", + "Hiking", + "Swimming", + "Bicycling", + "Kayaking", + "Canoeing", + "Water Skiing", + // 150 + "Cross-country Skiing", + "Downhill Skiing", + "Ice Skating", + "Dogsledding", + "Shooting", + "Golf Course", + "Ballpark", + // 157-182 added in PN-40 2.5 firmware + "Cache Found", + "Didn't Find It", + "My Cache", + // 160 + "Traditional Cache", + "Multi-Cache", + "Unknown Cache", + "Letterbox Hybrid", + "Whereigo Cache", + "Event Cache", + "Mega-Event Cache", + "Cache In Trash Out Event", + "EarthCache", + "Virtual Cache", + // 170 + "Webcam Cache", + "Waymark", + "NGS Benchmark", + "Write Note", + "Needs Maintenance", + "Final Location", + "Parking Area", + "Question to Answer", + "Reference Point", + "Stages of a Multicache", + // 180 + "Trailhead", + "Temporarily Disable Listing", + "Enable Listing", + // 183-222 added in PN-40 2.7 firmware + "Crane Truck", + "Forest Fire", + "Oil Derrick", + "Wind Turbine", + "Letter A", + "Letter B", + "Letter C", + // 190 + "Letter D", + "Letter E", + "Letter F", + "Letter G", + "Letter H", + "Letter I", + "Letter J", + "Letter K", + "Letter L", + "Letter M", + // 200 + "Letter N", + "Letter O", + "Letter P", + "Letter Q", + "Letter R", + "Letter S", + "Letter T", + "Letter U", + "Letter V", + "Letter W", + // 210 + "Letter X", + "Letter Y", + "Letter Z", + "Numeral 0", + "Numeral 1", + "Numeral 2", + "Numeral 3", + "Numeral 4", + "Numeral 5", + "Numeral 6", + // 220 + "Numeral 7", + "Numeral 8", + "Numeral 9" +}; + +static const char* +waypoint_symbol(unsigned i) +{ + const char* p = NULL; + if (i < sizeofarray(waypoint_symbol_name)) { + p = waypoint_symbol_name[i]; + } + return p; +} + +static unsigned +waypoint_symbol_index(const char* name) +{ + static unsigned last_result; + static char last_name[32]; + unsigned i = last_result; + + if (strncmp(name, last_name, sizeof(last_name)) != 0) { + i = sizeofarray(waypoint_symbol_name); + do { + i--; + } while (i != 0 && case_ignore_strcmp(name, waypoint_symbol_name[i]) != 0); + strncpy(last_name, name, sizeof(last_name)); + last_result = i; + } + return i; +} + +// vi: ts=4 sw=4 noexpandtab diff --git a/deprecated/google.cc b/deprecated/google.cc new file mode 100644 index 000000000..1f8a9b555 --- /dev/null +++ b/deprecated/google.cc @@ -0,0 +1,556 @@ +/* + Copyright (C) 2002-2014 Robert Lipe, robertlipe+source@gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA + + */ + + +#include "defs.h" +#include "xmlgeneric.h" +#include +#include +#include + +static char* encoded_points = NULL; +static char* encoded_levels = NULL; +static QString script; +static route_head** routehead; +static int* routecount; +static short_handle desc_handle; +static QString rd_fname; + +static int serial = 0; + +#define MYNAME "google" + +static xg_callback goog_points, goog_levels, goog_poly_e, goog_script; +static xg_callback goog_segment_s, goog_segment, goog_td_s, goog_td_b; +static xg_callback goog_td_e; + +static +xg_tag_mapping google_map[] = { + { goog_points, cb_cdata, "/page/directions/polyline/points" }, + { goog_levels, cb_cdata, "/page/directions/polyline/levels" }, + { goog_poly_e, cb_end, "/page/directions/polyline" }, + { goog_script, cb_cdata, "/html/head/script" }, + { goog_segment_s, cb_start, "/page/directions/segments/segment" }, + { goog_segment, cb_cdata, "/page/directions/segments/segment" }, + { goog_td_s, cb_start, "/div/table/tr/td" }, + { goog_td_s, cb_start, "/div/div/table/tr/td" }, + { goog_td_b, cb_cdata, "/div/table/tr/td/b" }, + { goog_td_b, cb_cdata, "/div/div/table/tr/td/b" }, + { goog_td_e, cb_end, "/div/table/tr/td" }, + { goog_td_e, cb_end, "/div/div/table/tr/td" }, + { NULL, (xg_cb_type)0, NULL } +}; + +void goog_script(xg_string args, const QXmlStreamAttributes*) +{ + script += args; +} + + +void goog_points(xg_string args, const QXmlStreamAttributes*) +{ + if (encoded_points) { + encoded_points = xstrappend(encoded_points, CSTRc(args)); + } else { + encoded_points = xstrdup(args); + } +} + + +void goog_levels(xg_string args, const QXmlStreamAttributes*) +{ + if (encoded_levels) { + encoded_levels = xstrappend(encoded_levels, CSTRc(args)); + } else { + encoded_levels = xstrdup(args); + } +} + +static char goog_segname[7]; +static QString goog_realname; +static int goog_segroute = 0; + +/* + * The segments contain an index into the points array. We use that + * index to find the waypoint and insert a better name for it. + */ +void goog_segment_s(xg_string args, const QXmlStreamAttributes* attrv) +{ + QStringRef ptidx = attrv->value("pointIndex"); + if (!ptidx.isEmpty()) { + snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x", + ptidx.toString().toUInt()); + } +} + +void goog_segment(xg_string args, const QXmlStreamAttributes*) +{ + Waypoint* wpt_tmp; + + if (routehead[goog_segroute]) { + wpt_tmp = route_find_waypt_by_name(routehead[goog_segroute], goog_segname); + if (wpt_tmp) { + wpt_tmp->shortname = mkshort(desc_handle, args); + wpt_tmp->description = args; + } + } +} + +void goog_td_s(xg_string args, const QXmlStreamAttributes* attrv) +{ + bool isdesc = false, isseg = false; + QStringRef aclass = attrv->value("class"); + QStringRef id = attrv->value("id"); + + if (aclass.isEmpty() || id.isEmpty()) { + return; + } + + isdesc = (aclass == "desc"); + isseg = (aclass == "dirsegtext"); + + if (isdesc) { + QStringRef subid(id.string(), id.position() + 6, id.length() - 6); + + goog_segroute = 0; + snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x", + subid.toString().toUInt()); + } else if (isseg) { + QString idstr = id.toString(); + int first_us; + + goog_segroute = 0; + + first_us = idstr.indexOf("_"); + if (idstr.indexOf("_", first_us + 1) != -1) { + goog_segroute = idstr.mid(first_us + 1).toUInt(); + } + + snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x", + idstr.mid(idstr.lastIndexOf("_") + 1).toUInt() + + routecount[goog_segroute]); + } +} + +void goog_td_b(xg_string args, const QXmlStreamAttributes*) +{ + if (goog_segname[0] == '\\' && !strchr(CSTRc(args), '\xa0')) { + goog_realname = args; + } +} + +void goog_td_e(xg_string args, const QXmlStreamAttributes*) +{ + if (goog_segname[0] == '\\' && !goog_realname.isEmpty()) { + goog_segment(goog_realname, NULL/*unused*/); + } + goog_segname[0] = '\0'; + goog_realname.clear(); +} + +static long decode_goog64(char** str) +{ + long result = 0; + unsigned char c = 0; + unsigned char shift = 0; + + if (!(**str)) { + return 0; + } + + do { + c = (unsigned char)(*(*str)++)-'?'; + result |= (c & 31)<latitude = lat / 100000.0; + wpt_tmp->longitude = lon / 100000.0; + wpt_tmp->route_priority=level; + wpt_tmp->shortname = QString().sprintf( "\\%5.5x", serial++); + route_add_wpt(routehead[goog_segroute], wpt_tmp); + } + } + +} + +static void +google_rd_init(const QString& fname) +{ + rd_fname = fname; + + desc_handle = mkshort_new_handle(); + setshort_length(desc_handle, 12); + + xml_init(fname, google_map, NULL); +} + +static void +goog_read_file(void) +{ + QFile src(rd_fname); + + src.open(QIODevice::ReadOnly); + + QTextStream tstr(&src); + tstr.setCodec("ISO-8859-1"); + + QString preamble = tstr.read(256); + QString needle("http-equiv=\"content-type\" content=\"text/html; charset="); + + if (!preamble.contains(needle)) { + // let QXmlStreamReader do its best if we can't figure it out... + xml_read(); + return; + } + + int idx = preamble.indexOf(needle); + QString charset = preamble.mid(idx + needle.length()); + + int endq = charset.indexOf('"'); + if (endq != -1) { + charset = charset.left(endq); + } + + QString wholefile; + if (charset == "ISO-8859-1") { + wholefile = preamble + tstr.readAll(); + } else { + tstr.reset(); + tstr.seek(0); + tstr.setCodec(CSTR(charset)); + wholefile = tstr.readAll(); + } + + xml_readunicode(wholefile); +} + +static void +google_read(void) +{ + routehead = (route_head**)xmalloc(sizeof(route_head*)); + routecount = (int*)xmalloc(sizeof(int)); + goog_segroute = 0; + routehead[goog_segroute] = NULL; + + goog_read_file(); + + xfree(routehead); + xfree(routecount); + routehead = NULL; + routecount = NULL; + + if (encoded_points) { + xfree(encoded_points); + encoded_points = NULL; + } + if (encoded_levels) { + xfree(encoded_levels); + encoded_levels = NULL; + } + if (!script.isEmpty()) { + // TODO: rethink with Qt to make this less dependent on strchr... + char* s = xstrdup(script); + char* xml = strchr(s, '\''); + char* dict = strstr(s, "({"); + char* end = NULL; + + if (xml && (!dict || (xml < dict))) { + routehead = (route_head**)xmalloc(sizeof(route_head*)); + routecount = (int*)xmalloc(sizeof(int)); + goog_segroute = 0; + xml++; + end = strchr(xml+1, '\''); + if (end) { + *end = '\0'; + xml_deinit(); + xml_init(NULL, google_map, NULL); + xml_readstring(xml); + if (encoded_points) { + xfree(encoded_points); + encoded_points = NULL; + } + if (encoded_levels) { + xfree(encoded_levels); + encoded_levels = NULL; + } + } + } else if (dict) { + char qc = '\''; + int ofs = 9; + int panelofs = 8; + int count = 0; + char* tmp = NULL; + char* start = NULL; + + char* panel = strstr(dict, "panel: '"); + encoded_points = strstr(dict, "points: '"); + encoded_levels = strstr(dict, "levels: '"); + if (!encoded_points) { + ofs = 10; + qc = '"'; + encoded_points = strstr(dict, "\"points\":\""); + encoded_levels = strstr(dict, "\"levels\":\""); + if (!encoded_points) { + encoded_points = strstr(dict, "points:\""); + encoded_levels = strstr(dict, "levels:\""); + ofs = 8; + } + } + + if (!panel) { + panel = strstr(dict, "panel:\""); + panelofs = 7; + } + tmp=encoded_points; + while (tmp) { + if (qc == '"') { + char* tmp1 = strstr(tmp, "\"points\":\""); + if (!tmp1) { + tmp1 = strstr(tmp, "points:\""); + } + tmp = tmp1; + } else { + tmp = strstr(tmp, "points: '"); + } + if (tmp) { + count++; + tmp++; + } + } + routehead = (route_head**)xmalloc(sizeof(route_head*)*count); + routecount = (int*)xmalloc(sizeof(int)*count); + goog_segroute = 0; + + do { + + if (encoded_points && encoded_levels) { + encoded_points += ofs; + encoded_levels += ofs; + end = strchr(encoded_points, qc); + if (end) { + *end = '\0'; + end = encoded_points; + while ((end = strstr(end, "\\\\"))) { + memmove(end, end+1, strlen(end)+1); + end++; + } + end = strchr(encoded_levels, qc); + if (end) { + start = end; + *end = '\0'; + end = encoded_levels; + while ((end = strstr(end, "\\\\"))) { + memmove(end, end+1, strlen(end)+1); + end++; + } + goog_poly_e(NULL, NULL); + + goog_segroute++; + start++; + { + encoded_points = strstr(start, "points: '"); + encoded_levels = strstr(start, "levels: '"); + } + if (!encoded_points) { + encoded_points = strstr(start, "\"points\":\""); + encoded_levels = strstr(start, "\"levels\":\""); + } + if (!encoded_points) { + encoded_points = strstr(start, "points:\""); + encoded_levels = strstr(start, "levels:\""); + } + } + } + } + } while (start && encoded_points && encoded_levels); + if (panel) { + panel += panelofs; + end = strstr(panel, "/table>
"); + } + if (!end) { + end = strstr(panel, "/div>
"); + } + } + if (end) { + char* to = panel; + char* from = panel; + while (*from) { + if (!strncmp(from, "\\\"", 2)) { + *to++ = '"'; + from += 2; + if (*(to-2) != '=') { + *to++ = ' '; + } + } else if (!strncmp(from, "\\042", 4)) { + *to++ = '"'; + from += 4; + + if (*(to-2) != '=') { + *to++ = ' '; + } + } else if (!strncmp(from, "\\u0026utm", 9)) { + strcpy(to, "&utm"); + to += 8; + from += 9; + } else if (!strncmp(from, "\\u0026", 6)) { + *to++='&'; + from += 6; + } else if (!strncmp(from, "\\u003c", 6)) { + *to++='<'; + from += 6; + } else if (!strncmp(from, "\\u003e", 6)) { + *to++='>'; + from += 6; + } else if (!strncmp(from, "\\x", 2)) { + unsigned int c; + sscanf(from+2, "%2x", &c); + *to++ = (char)c; + from += 4; + } else if (!strncmp(from, "\\'", 2)) { + *to++ = '\''; + from += 2; + } else if (!strncmp(from, " nowrap ", 8)) { + *to++ = ' '; + from += 8; + } else if (!strncmp(from, "tr style=\\\"display:none", 23)) { + if (strcmp(to-5, "/tr><")) { + /* broken 6-26-07 missing that apparently doesn't bother browsers */ + strcpy(to, "/tr><"); + to += 5; + } + *to++ = *from++; + } else { + *to++ = *from++; + } + } + *to = '\0'; + +#if 0 + { + FILE* foo = fopen("foo.xml", "w"); + fprintf(foo, "\n", xhtml_entities); + fwrite(panel, sizeof(char), strlen(panel), foo); + fclose(foo); + } +#endif + + xml_deinit(); + xml_init(NULL, google_map, NULL); + xml_readprefixstring(""); + xml_readstring(panel); + } + } + } + script.clear(); + xfree(routehead); + xfree(routecount); + xfree(s); + } + + /* + * 'Tis better to leak than crash when we are merging and + * don't see an 'end' in the first file. This feels a bit + * like plastering over a deeper problem... + * + */ + if (encoded_points) { + encoded_points = NULL; + } + if (encoded_levels) { + encoded_levels = NULL; + } +} + +static void +google_rd_deinit(void) +{ + xml_deinit(); + mkshort_del_handle(&desc_handle); + rd_fname.clear(); +} + +ff_vecs_t google_vecs = { + ff_type_file, + { ff_cap_none, ff_cap_read, ff_cap_none}, + google_rd_init, + NULL, + google_rd_deinit, + NULL, + google_read, + NULL, + NULL, + NULL, + CET_CHARSET_UTF8, 1 /* CET-REVIEW */ +}; diff --git a/google.cc b/google.cc deleted file mode 100644 index 1f8a9b555..000000000 --- a/google.cc +++ /dev/null @@ -1,556 +0,0 @@ -/* - Copyright (C) 2002-2014 Robert Lipe, robertlipe+source@gpsbabel.org - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA - - */ - - -#include "defs.h" -#include "xmlgeneric.h" -#include -#include -#include - -static char* encoded_points = NULL; -static char* encoded_levels = NULL; -static QString script; -static route_head** routehead; -static int* routecount; -static short_handle desc_handle; -static QString rd_fname; - -static int serial = 0; - -#define MYNAME "google" - -static xg_callback goog_points, goog_levels, goog_poly_e, goog_script; -static xg_callback goog_segment_s, goog_segment, goog_td_s, goog_td_b; -static xg_callback goog_td_e; - -static -xg_tag_mapping google_map[] = { - { goog_points, cb_cdata, "/page/directions/polyline/points" }, - { goog_levels, cb_cdata, "/page/directions/polyline/levels" }, - { goog_poly_e, cb_end, "/page/directions/polyline" }, - { goog_script, cb_cdata, "/html/head/script" }, - { goog_segment_s, cb_start, "/page/directions/segments/segment" }, - { goog_segment, cb_cdata, "/page/directions/segments/segment" }, - { goog_td_s, cb_start, "/div/table/tr/td" }, - { goog_td_s, cb_start, "/div/div/table/tr/td" }, - { goog_td_b, cb_cdata, "/div/table/tr/td/b" }, - { goog_td_b, cb_cdata, "/div/div/table/tr/td/b" }, - { goog_td_e, cb_end, "/div/table/tr/td" }, - { goog_td_e, cb_end, "/div/div/table/tr/td" }, - { NULL, (xg_cb_type)0, NULL } -}; - -void goog_script(xg_string args, const QXmlStreamAttributes*) -{ - script += args; -} - - -void goog_points(xg_string args, const QXmlStreamAttributes*) -{ - if (encoded_points) { - encoded_points = xstrappend(encoded_points, CSTRc(args)); - } else { - encoded_points = xstrdup(args); - } -} - - -void goog_levels(xg_string args, const QXmlStreamAttributes*) -{ - if (encoded_levels) { - encoded_levels = xstrappend(encoded_levels, CSTRc(args)); - } else { - encoded_levels = xstrdup(args); - } -} - -static char goog_segname[7]; -static QString goog_realname; -static int goog_segroute = 0; - -/* - * The segments contain an index into the points array. We use that - * index to find the waypoint and insert a better name for it. - */ -void goog_segment_s(xg_string args, const QXmlStreamAttributes* attrv) -{ - QStringRef ptidx = attrv->value("pointIndex"); - if (!ptidx.isEmpty()) { - snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x", - ptidx.toString().toUInt()); - } -} - -void goog_segment(xg_string args, const QXmlStreamAttributes*) -{ - Waypoint* wpt_tmp; - - if (routehead[goog_segroute]) { - wpt_tmp = route_find_waypt_by_name(routehead[goog_segroute], goog_segname); - if (wpt_tmp) { - wpt_tmp->shortname = mkshort(desc_handle, args); - wpt_tmp->description = args; - } - } -} - -void goog_td_s(xg_string args, const QXmlStreamAttributes* attrv) -{ - bool isdesc = false, isseg = false; - QStringRef aclass = attrv->value("class"); - QStringRef id = attrv->value("id"); - - if (aclass.isEmpty() || id.isEmpty()) { - return; - } - - isdesc = (aclass == "desc"); - isseg = (aclass == "dirsegtext"); - - if (isdesc) { - QStringRef subid(id.string(), id.position() + 6, id.length() - 6); - - goog_segroute = 0; - snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x", - subid.toString().toUInt()); - } else if (isseg) { - QString idstr = id.toString(); - int first_us; - - goog_segroute = 0; - - first_us = idstr.indexOf("_"); - if (idstr.indexOf("_", first_us + 1) != -1) { - goog_segroute = idstr.mid(first_us + 1).toUInt(); - } - - snprintf(goog_segname, sizeof(goog_segname), "\\%5.5x", - idstr.mid(idstr.lastIndexOf("_") + 1).toUInt() + - routecount[goog_segroute]); - } -} - -void goog_td_b(xg_string args, const QXmlStreamAttributes*) -{ - if (goog_segname[0] == '\\' && !strchr(CSTRc(args), '\xa0')) { - goog_realname = args; - } -} - -void goog_td_e(xg_string args, const QXmlStreamAttributes*) -{ - if (goog_segname[0] == '\\' && !goog_realname.isEmpty()) { - goog_segment(goog_realname, NULL/*unused*/); - } - goog_segname[0] = '\0'; - goog_realname.clear(); -} - -static long decode_goog64(char** str) -{ - long result = 0; - unsigned char c = 0; - unsigned char shift = 0; - - if (!(**str)) { - return 0; - } - - do { - c = (unsigned char)(*(*str)++)-'?'; - result |= (c & 31)<latitude = lat / 100000.0; - wpt_tmp->longitude = lon / 100000.0; - wpt_tmp->route_priority=level; - wpt_tmp->shortname = QString().sprintf( "\\%5.5x", serial++); - route_add_wpt(routehead[goog_segroute], wpt_tmp); - } - } - -} - -static void -google_rd_init(const QString& fname) -{ - rd_fname = fname; - - desc_handle = mkshort_new_handle(); - setshort_length(desc_handle, 12); - - xml_init(fname, google_map, NULL); -} - -static void -goog_read_file(void) -{ - QFile src(rd_fname); - - src.open(QIODevice::ReadOnly); - - QTextStream tstr(&src); - tstr.setCodec("ISO-8859-1"); - - QString preamble = tstr.read(256); - QString needle("http-equiv=\"content-type\" content=\"text/html; charset="); - - if (!preamble.contains(needle)) { - // let QXmlStreamReader do its best if we can't figure it out... - xml_read(); - return; - } - - int idx = preamble.indexOf(needle); - QString charset = preamble.mid(idx + needle.length()); - - int endq = charset.indexOf('"'); - if (endq != -1) { - charset = charset.left(endq); - } - - QString wholefile; - if (charset == "ISO-8859-1") { - wholefile = preamble + tstr.readAll(); - } else { - tstr.reset(); - tstr.seek(0); - tstr.setCodec(CSTR(charset)); - wholefile = tstr.readAll(); - } - - xml_readunicode(wholefile); -} - -static void -google_read(void) -{ - routehead = (route_head**)xmalloc(sizeof(route_head*)); - routecount = (int*)xmalloc(sizeof(int)); - goog_segroute = 0; - routehead[goog_segroute] = NULL; - - goog_read_file(); - - xfree(routehead); - xfree(routecount); - routehead = NULL; - routecount = NULL; - - if (encoded_points) { - xfree(encoded_points); - encoded_points = NULL; - } - if (encoded_levels) { - xfree(encoded_levels); - encoded_levels = NULL; - } - if (!script.isEmpty()) { - // TODO: rethink with Qt to make this less dependent on strchr... - char* s = xstrdup(script); - char* xml = strchr(s, '\''); - char* dict = strstr(s, "({"); - char* end = NULL; - - if (xml && (!dict || (xml < dict))) { - routehead = (route_head**)xmalloc(sizeof(route_head*)); - routecount = (int*)xmalloc(sizeof(int)); - goog_segroute = 0; - xml++; - end = strchr(xml+1, '\''); - if (end) { - *end = '\0'; - xml_deinit(); - xml_init(NULL, google_map, NULL); - xml_readstring(xml); - if (encoded_points) { - xfree(encoded_points); - encoded_points = NULL; - } - if (encoded_levels) { - xfree(encoded_levels); - encoded_levels = NULL; - } - } - } else if (dict) { - char qc = '\''; - int ofs = 9; - int panelofs = 8; - int count = 0; - char* tmp = NULL; - char* start = NULL; - - char* panel = strstr(dict, "panel: '"); - encoded_points = strstr(dict, "points: '"); - encoded_levels = strstr(dict, "levels: '"); - if (!encoded_points) { - ofs = 10; - qc = '"'; - encoded_points = strstr(dict, "\"points\":\""); - encoded_levels = strstr(dict, "\"levels\":\""); - if (!encoded_points) { - encoded_points = strstr(dict, "points:\""); - encoded_levels = strstr(dict, "levels:\""); - ofs = 8; - } - } - - if (!panel) { - panel = strstr(dict, "panel:\""); - panelofs = 7; - } - tmp=encoded_points; - while (tmp) { - if (qc == '"') { - char* tmp1 = strstr(tmp, "\"points\":\""); - if (!tmp1) { - tmp1 = strstr(tmp, "points:\""); - } - tmp = tmp1; - } else { - tmp = strstr(tmp, "points: '"); - } - if (tmp) { - count++; - tmp++; - } - } - routehead = (route_head**)xmalloc(sizeof(route_head*)*count); - routecount = (int*)xmalloc(sizeof(int)*count); - goog_segroute = 0; - - do { - - if (encoded_points && encoded_levels) { - encoded_points += ofs; - encoded_levels += ofs; - end = strchr(encoded_points, qc); - if (end) { - *end = '\0'; - end = encoded_points; - while ((end = strstr(end, "\\\\"))) { - memmove(end, end+1, strlen(end)+1); - end++; - } - end = strchr(encoded_levels, qc); - if (end) { - start = end; - *end = '\0'; - end = encoded_levels; - while ((end = strstr(end, "\\\\"))) { - memmove(end, end+1, strlen(end)+1); - end++; - } - goog_poly_e(NULL, NULL); - - goog_segroute++; - start++; - { - encoded_points = strstr(start, "points: '"); - encoded_levels = strstr(start, "levels: '"); - } - if (!encoded_points) { - encoded_points = strstr(start, "\"points\":\""); - encoded_levels = strstr(start, "\"levels\":\""); - } - if (!encoded_points) { - encoded_points = strstr(start, "points:\""); - encoded_levels = strstr(start, "levels:\""); - } - } - } - } - } while (start && encoded_points && encoded_levels); - if (panel) { - panel += panelofs; - end = strstr(panel, "/table>
"); - } - if (!end) { - end = strstr(panel, "/div>
"); - } - } - if (end) { - char* to = panel; - char* from = panel; - while (*from) { - if (!strncmp(from, "\\\"", 2)) { - *to++ = '"'; - from += 2; - if (*(to-2) != '=') { - *to++ = ' '; - } - } else if (!strncmp(from, "\\042", 4)) { - *to++ = '"'; - from += 4; - - if (*(to-2) != '=') { - *to++ = ' '; - } - } else if (!strncmp(from, "\\u0026utm", 9)) { - strcpy(to, "&utm"); - to += 8; - from += 9; - } else if (!strncmp(from, "\\u0026", 6)) { - *to++='&'; - from += 6; - } else if (!strncmp(from, "\\u003c", 6)) { - *to++='<'; - from += 6; - } else if (!strncmp(from, "\\u003e", 6)) { - *to++='>'; - from += 6; - } else if (!strncmp(from, "\\x", 2)) { - unsigned int c; - sscanf(from+2, "%2x", &c); - *to++ = (char)c; - from += 4; - } else if (!strncmp(from, "\\'", 2)) { - *to++ = '\''; - from += 2; - } else if (!strncmp(from, " nowrap ", 8)) { - *to++ = ' '; - from += 8; - } else if (!strncmp(from, "tr style=\\\"display:none", 23)) { - if (strcmp(to-5, "/tr><")) { - /* broken 6-26-07 missing that apparently doesn't bother browsers */ - strcpy(to, "/tr><"); - to += 5; - } - *to++ = *from++; - } else { - *to++ = *from++; - } - } - *to = '\0'; - -#if 0 - { - FILE* foo = fopen("foo.xml", "w"); - fprintf(foo, "\n", xhtml_entities); - fwrite(panel, sizeof(char), strlen(panel), foo); - fclose(foo); - } -#endif - - xml_deinit(); - xml_init(NULL, google_map, NULL); - xml_readprefixstring(""); - xml_readstring(panel); - } - } - } - script.clear(); - xfree(routehead); - xfree(routecount); - xfree(s); - } - - /* - * 'Tis better to leak than crash when we are merging and - * don't see an 'end' in the first file. This feels a bit - * like plastering over a deeper problem... - * - */ - if (encoded_points) { - encoded_points = NULL; - } - if (encoded_levels) { - encoded_levels = NULL; - } -} - -static void -google_rd_deinit(void) -{ - xml_deinit(); - mkshort_del_handle(&desc_handle); - rd_fname.clear(); -} - -ff_vecs_t google_vecs = { - ff_type_file, - { ff_cap_none, ff_cap_read, ff_cap_none}, - google_rd_init, - NULL, - google_rd_deinit, - NULL, - google_read, - NULL, - NULL, - NULL, - CET_CHARSET_UTF8, 1 /* CET-REVIEW */ -}; diff --git a/testo.d/deprecated/google.test b/testo.d/deprecated/google.test new file mode 100644 index 000000000..997cb4416 --- /dev/null +++ b/testo.d/deprecated/google.test @@ -0,0 +1,26 @@ +# +# Google Maps XML test +# +rm -f ${TMPDIR}/google.out +gpsbabel -i google -f ${REFERENCE}/google.xml -o csv -F ${TMPDIR}/google.out +compare ${REFERENCE}/google.csv ${TMPDIR}/google.out + +rm -f ${TMPDIR}/google.out +gpsbabel -i google -f ${REFERENCE}/google.js -o csv -F ${TMPDIR}/google.out +compare ${REFERENCE}/google.csv ${TMPDIR}/google.out + +rm -f ${TMPDIR}/google.out +gpsbabel -i google -f ${REFERENCE}/google_jan_06.html -o csv -F ${TMPDIR}/google.out +compare ${REFERENCE}/google_jan_06.csv ${TMPDIR}/google.out + +rm -f ${TMPDIR}/google.out +gpsbabel -i google -f ${REFERENCE}/google_multisegment.js -o gpx -F ${TMPDIR}/google.out +compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out + +rm -f ${TMPDIR}/google.out +gpsbabel -i google -f ${REFERENCE}/google_multisegment_utf8.js -o gpx -F ${TMPDIR}/google.out +compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out + +rm -f ${TMPDIR}/google.out +gpsbabel -i google -f ${REFERENCE}/google_multisegment_utf16.js -o gpx -F ${TMPDIR}/google.out +compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out diff --git a/testo.d/google.test b/testo.d/google.test deleted file mode 100644 index 997cb4416..000000000 --- a/testo.d/google.test +++ /dev/null @@ -1,26 +0,0 @@ -# -# Google Maps XML test -# -rm -f ${TMPDIR}/google.out -gpsbabel -i google -f ${REFERENCE}/google.xml -o csv -F ${TMPDIR}/google.out -compare ${REFERENCE}/google.csv ${TMPDIR}/google.out - -rm -f ${TMPDIR}/google.out -gpsbabel -i google -f ${REFERENCE}/google.js -o csv -F ${TMPDIR}/google.out -compare ${REFERENCE}/google.csv ${TMPDIR}/google.out - -rm -f ${TMPDIR}/google.out -gpsbabel -i google -f ${REFERENCE}/google_jan_06.html -o csv -F ${TMPDIR}/google.out -compare ${REFERENCE}/google_jan_06.csv ${TMPDIR}/google.out - -rm -f ${TMPDIR}/google.out -gpsbabel -i google -f ${REFERENCE}/google_multisegment.js -o gpx -F ${TMPDIR}/google.out -compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out - -rm -f ${TMPDIR}/google.out -gpsbabel -i google -f ${REFERENCE}/google_multisegment_utf8.js -o gpx -F ${TMPDIR}/google.out -compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out - -rm -f ${TMPDIR}/google.out -gpsbabel -i google -f ${REFERENCE}/google_multisegment_utf16.js -o gpx -F ${TMPDIR}/google.out -compare ${REFERENCE}/google_multisegment.gpx ${TMPDIR}/google.out